Skip to content

Commit 01700dc

Browse files
fix(app-shell): inspector literals with an existing catalogue row read it (objectui#10696) (#10741)
Fixes #10696 Clause-②: no A zh-CN author read `Search fields…` in the dashboard widget inspector's filter-binding field picker, while the designer catalogue already carried `engine.form.searchFields` in en and zh and `DatasetDefaultInspector` read it for the same combo. That site now reads the row. The census the triage asked for found seven more inspector sites passing an English literal where an `engine.*` row with the same English already existed; each now reads its row. No catalogue row is added or changed, so the objectui#4662 `engine.*` carve-out does not move, and en-US renders the same words as before. This branch resumes the work a previous `os-dev` left at `68eedfed1` (release note `5851632349`); nothing it did was taken as verified. Round 2 (`7e724988e3`) adds the site the contract review of `68eedfed1` found missed: the flow node inspector's nested-node breadcrumb. ## Premise check — H1, measured under zh-CN Base `383a34f52` (the branch point), pin from this branch: `DashboardWidgetInspector`'s filter-binding combo search box rendered the English literal under zh-CN — `Expected: "搜索字段…"`, `Received: "Search fields…"`. The lit control on the same base, `DatasetDefaultInspector`'s dimension-field combo, rendered `搜索字段…` (its case passed on base). H1 holds as the card stated it. ## Census — H3, over `metadata-admin/inspectors/*.ts` and `*.tsx` (44 source files: the 27 components and the 17 helpers whose strings they render) Method: an AST walk (TypeScript compiler API) over every JSX attribute value (direct, or inside a ternary / template / binary expression), every JSX text and expression child, and every object-literal text prop (`label`, `placeholder`, `title`, `aria-label`, `emptyText`, `message`, `help`, and the like), plus a broad pass over every string literal in those files so a word that flows into a label through a variable or a helper's return is seen too; class-name-shaped and key-shaped strings dropped. Each surviving literal was compared byte-for-byte against the EN values of `metadata-admin/i18n.ts`. Both passes ran on the base tree and on this head. Counts: the default pass keeps 289 of 1282 candidate literals on base and 283 of 1276 on head; the broad pass keeps 1989 of 3821 on base and 1982 of 3814 on head. The rows that disappear between base and head are the seven literals fixed at `68eedfed1`. The eighth site is different in kind: `flow-nested-selection.ts` `regionLabelOf` still returns the English `Try` / `Catch` / `Branch N` (it is the pure, i18n-free mirror of the canvas layout's own fallbacks, and its test pins that), so the census still lists those literals on head with their same-value rows — what changed is their reader, which now translates them at render. The first round's census walked `*.tsx` only and missed that helper; the review of `68eedfed1` found it. Sites are cited by file and enclosing symbol or prop, not by line, per AGENTS.md #11 (a pull-request body has no same file). ### Fixed — a row with the same English existed; the site now reads it | file · site | literal before | row read now | |:--|:--|:--| | `DashboardWidgetInspector` · filter-binding `InspectorComboField` `searchPlaceholder` | `Search fields…` | `engine.form.searchFields` | | `ViewVariantInspector` · `ViewObjectPicker` `searchPlaceholder` (now a prop its one caller fills) | `Search objects…` | `engine.inspector.dataset.searchObjects` | | `ObjectFieldInspector` · `LookupConfigFields` dependsOn chip, remove button `aria-label` | `Remove NAME` | `engine.form.removeNamed` | | `ObjectFieldInspector` · `LookupConfigFields` filter row value `placeholder` | `value` | `engine.inspector.condition.valuePlaceholder` | | `ObjectFieldInspector` · `SummaryConfigFields` filter row value `placeholder` | `value` | `engine.inspector.condition.valuePlaceholder` | | `FlowEdgeInspector` · decision-branch picker, custom row `label` | `— Custom —` | `engine.inspector.flowEdge.branchCustom` (the approval-branch picker beside it already read it) | | `FlowEdgeInspector` · decision-branch picker, unnamed branch name (`nm`) | `Branch N` | `engine.flowRegion.branchN` | | `FlowNodeInspector` · nested-node breadcrumb, region crumb (label from `flow-nested-selection.ts` `regionLabelOf`) — round 2 | `Try`, `Catch`, `Branch N` | `engine.flowRegion.try`, `.catch`, `.branchN`, through `displayRegionLabel` (`previews/flow-region-label.ts`; the canvas region header in `previews/flow-region-view.tsx` reads the same function) | ### Listed for the seat — English words with no row of the same value (⛔ no key added here) | file · site | literal | nearest row | |:--|:--|:--| | `DashboardWidgetInspector` · `WIDGET_TYPES[].label` (the widget-type select) | `KPI Metric`, `Bar Chart`, `Horizontal Bar`, `Line Chart`, `Area Chart`, `Pie Chart`, `Donut Chart`, `Funnel`, `Table`, `Pivot Table` | none (`engine.inspector.view.type.*` covers view types, not widget types) | | `DashboardWidgetInspector` · `InspectorEmpty` `message` | `Unsupported selection kind: KIND`, `The selected widget was removed from the draft.` | none | | `DashboardWidgetInspector` header, `DashboardDefaultInspector` add-widget title and widget list | `Widget N` (untitled-widget fallback, 3 sites) | none (`engine.flowRegion.branchN` is `Branch {n}`, another word) | | `ObjectFieldInspector` · `LOOKUP_OPERATORS[].label` | `= equals`, `≠ not equals`, the greater-than and less-than rows, `≥ at least`, `≤ at most`, `contains`, `in (any of)`, `not in` | partial: `engine.inspector.condition.op.equals` / `.notEquals` / `.greaterThan` / `.lessThan` carry the words without the symbol prefix; no row for at least, at most, contains, in, not in | | `FlowEdgeInspector` · decision-branch option `suffix` | ` · default` | none (`engine.inspector.flowEdge.typeDefault` is `Normal`; `engine.inspector.pageBlock.option.colorVariant.default` is `Default`, another meaning) | | `FlowNodeInspector` · nested-location `aria-label` | `nested node location` | none | | `FlowNodeInspector` · `commitAdvanced`, shown through `setAdvError` | `Must be a JSON object` | none | | `VariableTextInput` · `PICK_LABEL`, `SEARCH_LABEL`, `EMPTY_LABEL` | `Insert a reference`, `Search references…`, `No matching references.` | none | | `flow-nested-selection.ts` · `regionLabelOf` for the `body` region (the loop-body crumb) | `Body` | none of the same concept (the two `Body` rows, `engine.inspector.pageBlock.option.variant.body` and `engine.inspector.pageBlock.field.record:alert.body`, are page-block body text) | | `useFlowScope.ts` · `GROUP_LABELS` and the three approval scope-group headings (rendered as `VariableTextInput` `CommandGroup` headings) | `Flow variables`, `Upstream outputs`, `Loop item`, `Trigger record`; `Current record (live at node entry)`, `Trigger snapshot (at submit)`, `Flow variables`; the `pre-update row` detail | none | | `expression-validate.ts` · `validateExpressionClient` messages (rendered by `FlowExprIssue` and the edge condition) | `Unbalanced parentheses in …`, `Unbalanced brackets in …`, `Expected a CEL expression but got a … dialect.`, `Expected a text template but got a … expression.`, the single-brace and map-literal explanations, `Conditions are bare CEL (e.g. …).` | none | ### Not words, or another channel — left literal, as PR objectui#10678 and PR objectui#10651 ruled for this family - Code samples and syntax hints in `placeholder`s: `record.amount * 0.2`, `record.status != 'draft'`, the arrow-function samples in the hook and action script boxes, `comma,separated,values`, the reference-syntax hint in `FlowObjectListField` (`current.FIELD · trigger.FIELD · vars.NODE.KEY`), `/api/v1/…`, `{ }`, `building` (an icon name), the CEL samples in `flow-node-config.ts`. - Sample values: `status`, `done` (its byte match with `engine.flowSim.status.done` is a coincidence — that row names a simulator status, this is a sample value in a field), `100`, `0`, `255`, `10`. - Stored values and type names rendered as code, or compared rather than shown: HTTP methods, `script`, currency codes (`USD ($)` …), the scope roots `previous` / `previous.FIELD`, `columns[N]`, the operator names in `datasetFilterCondition.ts` (`equals` is a stored operator, not a label), the type tokens `string` / `number` / `boolean` / `expression` / `true` the helpers compare against. - `flow-node-config.ts` field labels, help and option labels (`Trigger`, `Object`, `Record created`, `Cron schedule` …): translated at render through `localizeField` from `FLOW_FIELD_ZH` — a different channel, not this card's class. - `ViewVariantInspector`'s `TYPE_LABELS` map: read only after `t('engine.inspector.view.type.' + type)` echoes the key back, i.e. for a view type the catalogue lacks — a fallback behind the row, not a literal passed where a row exists. - `DashboardDefaultInspector`'s `New widget` / `New TYPE`: the default title written into the draft — author data, not chrome (the objectui#10678 boundary). ## Round 2 — the nested-node breadcrumb (`7e724988e3`) The review of `68eedfed1` (PR comment `5852290145`) found that `flow-nested-selection.ts` `regionLabelOf` returns `Try`, `Catch` and `Branch N`, `locateFlowNode` stores it as `regionLabel`, and `FlowNodeInspector` renders it in the `aria-label="nested node location"` crumb, while `engine.flowRegion.try` / `.catch` / `.branchN` exist with the same English and the canvas region header already reads them — so a zh-CN author saw `尝试` on the canvas and `Try` in the inspector. **Shape chosen: carry the region key out of `locateFlowNode` and translate at the render site.** `NodeLocation` gains `regionKey` (the key `branch-N` carries the branch index), set beside the unchanged English `regionLabel`; `FlowNodeInspector` renders `displayRegionLabel({ key, label }, locale)` with the `locale` it already holds. The pure codec stays English and i18n-free (its own test pins that it mirrors the canvas layout's fallbacks), and the translation happens once, where the locale is. Threading a locale into the helper would have made every `locateFlowNode` caller locale-aware and duplicated the canvas's mapping in the codec. **Reused `displayRegionLabel` itself, no cycle.** It was a module-private function of `previews/flow-region-view.tsx`; it moved, with its body unchanged (the signature gains `export` and takes `Pick` of `key` and `label`), into `previews/flow-region-label.ts`, a leaf module whose only runtime import is `../i18n.js` (the `LabeledRegion` import is type-only). `FlowRegionView` and `FlowNodeInspector` both import it, so the mapping is stated once and the canvas header and the crumb read one word. A leaf `.ts` module rather than an `export` from the component `.tsx`, so no `react-refresh/only-export-components` warning is added; the parameter is `Pick` of `key` and `label`, so both callers fit. `Body` has no same-concept row and passes through, as does an authored branch name (the function's existing `Branch N` gate); en output is byte-identical (the en rows are the literals). Round-2 name-status against `68eedfed1`: M `.changeset/10696-inspector-literals-read-existing-keys.md` · M `inspectors/FlowNodeInspector.tsx` · M `inspectors/flow-nested-selection.test.ts` · M `inspectors/flow-nested-selection.ts` · M `inspectors/inspectorStrings.i18n-10696.test.tsx` · A `previews/flow-region-label.ts` · M `previews/flow-region-view.tsx`. Numstat: changeset +8 −2 · FlowNodeInspector +7 −1 · flow-nested-selection.test +2 −0 · flow-nested-selection +12 −0 · pin +105 −0 · flow-region-label +35 −0 · flow-region-view +5 −20; 7 files, +174 −23. ## Pins — red on base, green on head, per-site ablation `inspectors/inspectorStrings.i18n-10696.test.tsx`, 22 cases: one zh case per fixed site reading the rendered attribute or text (the open combo's search box, a button's `aria-label`, an input's `placeholder`, the select trigger's text, the crumb's region segment) against the catalogue's zh row behind a guard (not the echoed key, not the en row); an en case per site; and a lit control on the same mount, already translated before this change (`DatasetDefaultInspector` for the card's own control; the nested-id hint for the breadcrumb). The breadcrumb block's outside control is the existing en crumb test in `FlowNodeInspector.test.tsx` (`opens a nested node with a container › region › node breadcrumb and its config fields`), unchanged and green. - Head `7e724988e3`: the pin, `FlowNodeInspector.test.tsx`, `flow-nested-selection.test.ts`, `FlowCanvas.test.tsx`, `flow-canvas-layout.test.ts` and `flow-region-metrics.test.ts` together: `Test Files 6 passed (6)`, `Tests 99 passed (99)`, exit 0 (the pin's 22 among them). Round 1 head `68eedfed1`: `Tests 17 passed (17)`. - Base leg, round 2 — the six inspector-side sources (`DashboardWidgetInspector`, `FlowEdgeInspector`, `ObjectFieldInspector`, `ViewVariantInspector`, `FlowNodeInspector`, `flow-nested-selection.ts`) from `383a34f52`, pin from head: `Tests 10 failed | 12 passed (22)`, exit 1 — the ten failures are the seven zh cases of round 1 (`Received: "Search fields…"`, `"Search objects…"`, `"Remove region"`, `"value"` twice, `"— Custom —"`, `"Branch 1 · record.amount > 100"`) plus the three zh crumb cases (`Received: "Try"`, `"Catch"`, `"Branch 2"`); the en cases and the lit controls pass. Restore proven: each path's `git hash-object` equals its HEAD blob and `git diff HEAD` is 0 lines. Round 1's base leg (four sources) read `Tests 7 failed | 10 passed (17)`. - Per-site ablation, with objectstack's `scripts/ablation-replace.mjs` (one anchor declared to hit once; the write and the restore judged by blob hash on disk): each of the eight sites with its literal put back alone fails exactly its own zh case(s) and restores to the HEAD blob. Round 1, S1–S7: `1 failed | 16 passed (17)` each — S1 `searchFields`; S2 `searchObjects` (mutated inside `ViewObjectPicker`, so the passed prop is ignored); S3 `removeNamed`; S4 lookup `valuePlaceholder` (the summary case stays green); S5 summary `valuePlaceholder` (the lookup case stays green); S6 `branchCustom` (anchor lengthened to the decision-branch block, since the approval-branch picker carries the same row); S7 `branchN`. Round 2, S8 — the crumb back to `{loc?.regionLabel}`: `Tests 3 failed | 19 passed (22)`, exactly the three zh crumb cases (try, catch, unnamed branch); the en crumb case, the lit control and the other 17 stay green. - Two no-op attempts in round 1, recorded rather than hidden: the driver's first call ran the tool from outside the repository (refused before writing; hashes unchanged), and the first S2 reinstated the wrong text at the caller (`ReferenceError`, so zh and en fell over together — a broken-harness shape, not a reading). Both were redone; the readings above are from the redone legs. ## Gates — at `7e724988e3`; heavy runs through `os-verify-lock` | gate | exit | reading | |:--|:--|:--| | `pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...' run build` (the closure, so `tsc` reads real `dist/*.d.ts`) | 0 | | | `tsc --noEmit` and `tsc -p tsconfig.test.json` in `packages/app-shell`, run directly | 0, 0 | 0 errors; `--listFiles` names the pin once in the test project | | `pnpm exec vitest run` over the six files above | 0 | `Test Files 6 passed (6)`, `Tests 99 passed (99)` | | round 1, at `68eedfed1`: `metadata-admin/inspectors/` (103 files), the rest of `metadata-admin/` in two halves (105 + 105 = all 313 tracked test files), `views/studio-design/` (65 files) | 0 each | `1428 passed | 1 skipped`, `881 passed`, `1287 passed`, `359 passed` | | eslint, plain form, on the 6 TS files round 2 touches | 0 | `--format json`: 6 files, 0 errors, 3 warnings — `FlowNodeInspector.tsx`'s pre-existing `no-explicit-any` and `react-hooks/set-state-in-effect`, equal to its blob at `68eedfed1` (`--stdin`); the new module, the codec, its test and the pin have 0. Round 1's 5 files: 0 errors, 26 warnings, per file equal to base | | `check-changeset-presence` | 0 | 10 source files of 1 released package, 1 changeset | | `check:changeset-claims` (base `383a34f52`, head) | 0 | no pending changeset names a file this change touches | | `changeset:check` (fixed group, no major) | 0 | | | `check:new-line-citations` | 0 | 0 new citations | | `check:control-bytes` | 0 | OK | | `check:i18n-keys`, `check:i18n-drift`, `check:i18n-dead-keys` | 0 each | drift: 0 en values changed, 0 keys added or removed in the designer table | | `check:vi-mock-specifiers`, `check:vi-mock-inherit`, `check:vi-mock-override-shape`, `check:test-path-roots` | 0 each | the pin's two new `vi.mock` calls resolve | | round 1 only: `check:i18n-designer-parity`, `check:pending-changeset-literals` | 0 each | | - The eslint narrowing is a measurement: the population is the changed `.ts`/`.tsx` files, which the config's `**/*.{ts,tsx}` block covers; the JSON output counts the files; `eslint.config.js` configures no type-aware linting (a grep for `projectService` or a `parserOptions` project exits 1), so this diff cannot move an untouched file's verdict. - The test narrowing: the inspectors are reached only through `inspectors/index.ts` and the inspector registry; `displayRegionLabel`'s two importers are `flow-region-view.tsx` (whose canvas tests ran) and `FlowNodeInspector.tsx`; the package's `exports` do not change. The rest of `packages/app-shell` and the repo-wide `pnpm lint` / `pnpm test` shards belong to CI. - NOT MEASURED: `check:eager-locale-catalogues` and the eager-closure budget, which read a built `apps/console` bundle (`performance-budget.yml` runs both). ## Published prose — H5 Pending changesets that name these strings: `10586-inspector-strings-i18n.md` (PR 1 of the family: "plus the existing `engine.form.add` and `engine.form.searchFields`… The dataset inspector reads the `locale` its host already passes") and `10586-inspector-strings-i18n-2.md` (PR 2: the combo's defaults, "A label the caller passes still wins"). Neither sentence is falsified: this change adds callers that pass a translated label, which is exactly the case both describe. No dated note is needed and no frontmatter moved. This PR's own changeset now titles eight words and carries a bullet for the breadcrumb naming the canvas header as the rows' existing reader. ## Serial `origin/main` `1fc77fd806` (fetched into a private per-worktree ref just before this round's push; re-read at `ee6f6c6a45` by the contract review, still clean) touches none of this branch's files since the base `383a34f52`, and `git merge-tree --write-tree` against it is clean (exit 0). Not merged. ## Acceptance notes - The seat's list above (widget-type labels, `InspectorEmpty` messages, `Widget N`, the lookup operator labels, ` · default`, the nested-location `aria-label`, `Must be a JSON object`, `VariableTextInput`'s three labels, the `Body` region crumb, `useFlowScope`'s group headings, `expression-validate`'s client messages) needs new rows, which this card forbids; it is the family's remaining English under zh, read from source with the pin's harness as the instrument. - `ViewVariantInspector`'s `ViewObjectPicker` takes `searchPlaceholder` as a required prop rather than reading `useMetadataLocale()` itself, because its host already carries `locale` — the `locale` prop channel PR objectui#10678 named for hosted inspectors. - `flow-region-view.tsx` loses its private `displayRegionLabel` and imports the moved one; its behaviour is unchanged and `FlowCanvas.test.tsx`, `flow-canvas-layout.test.ts` and `flow-region-metrics.test.ts` ran green. --- _Generated by [Claude Code](https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent a2f361b commit 01700dc

11 files changed

Lines changed: 587 additions & 29 deletions
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
---
2+
'@object-ui/app-shell': patch
3+
---
4+
5+
fix(app-shell): eight inspector words that already had a catalogue row read it, so zh-CN authors see Chinese there (objectui#10696)
6+
7+
A few designer inspector props passed an English literal although the designer's
8+
own string catalogue already carried the same English under a key with a zh row.
9+
So a zh-CN author read English on an otherwise Chinese inspector:
10+
11+
- the dashboard widget inspector's filter-binding field picker, whose search box
12+
read `Search fields…`; it now reads `engine.form.searchFields`, the row the
13+
dataset inspector's field pickers already read;
14+
- the view inspector's object picker, whose search box read `Search objects…`; it
15+
now reads `engine.inspector.dataset.searchObjects`, the row the dataset
16+
inspector's object picker already reads;
17+
- the field inspector's lookup `dependsOn` chips, whose remove buttons were named
18+
`Remove NAME`; they now read `engine.form.removeNamed`;
19+
- the field inspector's lookup and roll-up filter rows, whose value input read
20+
`value`; it now reads `engine.inspector.condition.valuePlaceholder`, the row the
21+
condition builder's value input reads;
22+
- the flow edge inspector's decision-branch picker, whose custom entry read
23+
`— Custom —` and whose unnamed branches read `Branch N`; they now read
24+
`engine.inspector.flowEdge.branchCustom` (the row the approval-branch picker
25+
beside it already reads) and `engine.flowRegion.branchN`;
26+
- the flow node inspector's container › region › node breadcrumb for a node
27+
nested in a `try_catch` or `parallel` region, whose region crumb read `Try`,
28+
`Catch` or `Branch N`; it now reads `engine.flowRegion.try`, `.catch` and
29+
`.branchN` through the same `displayRegionLabel` the canvas region header
30+
already reads, so the canvas and the inspector show the same word. `Body` and
31+
an authored branch name pass through as before.
32+
33+
No catalogue row is added or changed, and the en-US text is unchanged.

‎packages/app-shell/src/views/metadata-admin/inspectors/DashboardWidgetInspector.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -439,7 +439,7 @@ export function DashboardWidgetInspector({
439439
onCommit={(v) => setBinding(v ? v : undefined)}
440440
options={fieldComboOptions}
441441
placeholder={tFormat('engine.inspector.widget.filterBindingDefault', locale, { field: def.field })}
442-
searchPlaceholder="Search fields…"
442+
searchPlaceholder={t('engine.form.searchFields', locale)}
443443
disabled={readOnly}
444444
mono
445445
/>

‎packages/app-shell/src/views/metadata-admin/inspectors/FlowEdgeInspector.tsx‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
import * as React from 'react';
1818
import type { MetadataInspectorProps } from '../inspector-registry.js';
19-
import { t } from '../i18n.js';
19+
import { t, tFormat } from '../i18n.js';
2020
import {
2121
InspectorShell,
2222
InspectorTextField,
@@ -187,11 +187,11 @@ export function FlowEdgeInspector({ selection, draft, onPatch, onClearSelection,
187187
options={[
188188
...branches.map((b, i) => {
189189
const expr = branchExpr(b);
190-
const nm = branchName(b) || `Branch ${i + 1}`;
190+
const nm = branchName(b) || tFormat('engine.flowRegion.branchN', locale, { n: i + 1 });
191191
const suffix = expr === '' || expr === 'true' ? ' \u00b7 default' : ` \u00b7 ${expr}`;
192192
return { value: String(i), label: `${nm}${suffix}` };
193193
}),
194-
{ value: '', label: '\u2014 Custom \u2014' },
194+
{ value: '', label: t('engine.inspector.flowEdge.branchCustom', locale) },
195195
]}
196196
onCommit={applyBranch}
197197
disabled={readOnly}

‎packages/app-shell/src/views/metadata-admin/inspectors/FlowNodeInspector.tsx‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ import { FlowNodeConfigField } from './FlowNodeConfigField.js';
5656
import { useFlowScope } from './useFlowScope.js';
5757
import { nodeOutputRefs, type ScopeRef } from './flow-scope.js';
5858
import { NESTED_NODE_KIND, parseNestedNodeId, locateFlowNode, type InspectorFlowNode } from './flow-nested-selection.js';
59+
import { displayRegionLabel } from '../previews/flow-region-label.js';
5960
import type { FlowDesignerEdge } from '../previews/flow-canvas-layout.js';
6061
import { ScreenPreview } from '../previews/ScreenPreview.js';
6162

@@ -388,7 +389,12 @@ export function FlowNodeInspector({ selection, draft, onPatch, onClearSelection,
388389
<div className="flex flex-wrap items-center gap-1 text-[11px] text-muted-foreground" aria-label="nested node location">
389390
<span className="max-w-[45%] truncate font-medium">{loc?.container?.label || loc?.container?.id}</span>
390391
<span aria-hidden>›</span>
391-
<span className="truncate">{loc?.regionLabel}</span>
392+
{/* The region's English structural fallback (`Try` / `Catch` / `Branch N`)
393+
reads the same catalogue rows the canvas header above it reads;
394+
`Body` and an authored branch name pass through (objectui#10696). */}
395+
<span className="truncate">
396+
{loc?.regionKey ? displayRegionLabel({ key: loc.regionKey, label: loc.regionLabel }, locale) : loc?.regionLabel}
397+
</span>
392398
<span aria-hidden>›</span>
393399
<span className="max-w-[45%] truncate font-medium text-foreground">{node.label || node.id}</span>
394400
</div>

‎packages/app-shell/src/views/metadata-admin/inspectors/ObjectFieldInspector.tsx‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1714,7 +1714,7 @@ function LookupConfigFields({
17141714
label={tr('designer.field.lookup.filterValue')}
17151715
value={valueToText(f.value)}
17161716
onCommit={(v) => patchFilter(i, { value: textToValue(f.operator, v) })}
1717-
placeholder={f.operator === 'in' || f.operator === 'notIn' ? 'comma,separated,values' : 'value'}
1717+
placeholder={f.operator === 'in' || f.operator === 'notIn' ? 'comma,separated,values' : tr('engine.inspector.condition.valuePlaceholder')}
17181718
disabled={readOnly}
17191719
mono
17201720
/>
@@ -1732,7 +1732,7 @@ function LookupConfigFields({
17321732
<span key={n} className="inline-flex items-center gap-1 rounded bg-secondary px-2 py-0.5 text-[11px] font-mono">
17331733
{n}
17341734
{!readOnly && (
1735-
<button type="button" className="text-muted-foreground hover:text-foreground" onClick={() => removeDependsOn(n)} aria-label={`Remove ${n}`}>×</button>
1735+
<button type="button" className="text-muted-foreground hover:text-foreground" onClick={() => removeDependsOn(n)} aria-label={tFormat('engine.form.removeNamed', locale, { name: n })}>×</button>
17361736
)}
17371737
</span>
17381738
))}
@@ -2048,7 +2048,7 @@ function SummaryConfigFields({
20482048
label={tr('designer.field.lookup.filterValue')}
20492049
value={summaryValueToText(f.value)}
20502050
onCommit={(v) => patchFilterRow(i, { value: v })}
2051-
placeholder={f.operator === 'in' || f.operator === 'notIn' ? 'comma,separated,values' : 'value'}
2051+
placeholder={f.operator === 'in' || f.operator === 'notIn' ? 'comma,separated,values' : tr('engine.inspector.condition.valuePlaceholder')}
20522052
disabled={readOnly}
20532053
mono
20542054
/>

‎packages/app-shell/src/views/metadata-admin/inspectors/ViewVariantInspector.tsx‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,15 @@ function ViewObjectPicker({
5252
value,
5353
onCommit,
5454
placeholder,
55+
searchPlaceholder,
5556
disabled,
5657
}: {
5758
label: string;
5859
value: string;
5960
onCommit: (v: string) => void;
6061
placeholder?: string;
62+
/** The search box's words, in the caller's locale (objectui#10696). */
63+
searchPlaceholder: string;
6164
disabled?: boolean;
6265
}) {
6366
const { options, loading } = useObjectOptions();
@@ -69,7 +72,7 @@ function ViewObjectPicker({
6972
options={options.map((o) => ({ value: o.name, label: o.label }))}
7073
loading={loading}
7174
placeholder={placeholder}
72-
searchPlaceholder="Search objects…"
75+
searchPlaceholder={searchPlaceholder}
7376
disabled={disabled}
7477
mono
7578
/>
@@ -438,6 +441,7 @@ export function ViewVariantInspector({
438441
value={binding.value}
439442
onCommit={setObject}
440443
placeholder={t('engine.inspector.view.objectPlaceholder', locale)}
444+
searchPlaceholder={t('engine.inspector.dataset.searchObjects', locale)}
441445
disabled={readOnly}
442446
/>
443447

‎packages/app-shell/src/views/metadata-admin/inspectors/flow-nested-selection.test.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ describe('flow-nested-selection — locateFlowNode + write-back', () => {
151151
expect(loc.nested).toBe(true);
152152
expect(loc.scopeAnchorId).toBe('each'); // container, not the nested node
153153
expect(loc.container?.id).toBe('each');
154+
expect(loc.regionKey).toBe('body'); // carried for the render site's translation (objectui#10696)
154155
expect(loc.regionLabel).toBe('Body');
155156
expect(loc.node.id).toBe('charge');
156157
});
@@ -173,6 +174,7 @@ describe('flow-nested-selection — locateFlowNode + write-back', () => {
173174
const d = draft();
174175
const id = encodeNestedNodeId({ containerId: 'fan', regionKey: 'branch-1', nodeId: 'c' });
175176
const loc = locateFlowNode(d, { kind: NESTED_NODE_KIND, id })!;
177+
expect(loc.regionKey).toBe('branch-1');
176178
expect(loc.regionLabel).toBe('Branch 2');
177179
const patch = loc.write({ ...loc.node, label: 'Notify CRM' })!;
178180
const nodes = patch.nodes as Array<Record<string, unknown>>;

‎packages/app-shell/src/views/metadata-admin/inspectors/flow-nested-selection.ts‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,11 @@ export function regionConfigPathOf(regionKey: string): RegionConfigPath | null {
8181
* `extractRegions`' header labels: `Body` / `Try` / `Catch`, and for a parallel
8282
* branch the authored `name` or the 1-based `Branch N` fallback (read from the
8383
* container so the label matches what the canvas header shows).
84+
*
85+
* English, like `extractRegions`' own fallbacks: this codec is pure and
86+
* i18n-free. The render site translates the structural fallbacks through
87+
* `displayRegionLabel` (`../previews/flow-region-label.ts`), keyed off the
88+
* region key it receives beside this label (objectui#10696).
8489
*/
8590
export function regionLabelOf(regionKey: string, container?: { config?: unknown } | null): string {
8691
if (regionKey === 'body') return 'Body';
@@ -165,6 +170,12 @@ export interface NodeLocation {
165170
scopeAnchorId: string;
166171
/** The enclosing container node — only when nested. */
167172
container?: InspectorFlowNode;
173+
/**
174+
* The region key (`body` / `try` / `catch` / `branch-N`) — only when nested.
175+
* Carried beside `regionLabel` so the render site can translate the English
176+
* structural fallback through `displayRegionLabel` (objectui#10696).
177+
*/
178+
regionKey?: string;
168179
/** Human region label for the inspector breadcrumb — only when nested. */
169180
regionLabel?: string;
170181
/**
@@ -256,6 +267,7 @@ export function locateFlowNode(
256267
nested: true,
257268
scopeAnchorId: container.id,
258269
container,
270+
regionKey: path.regionKey,
259271
regionLabel: regionLabelOf(path.regionKey, container),
260272
write: (next) => writeNestedNode(nodes, containerIdx, container, rp, nodeIdx, next),
261273
};

0 commit comments

Comments
 (0)