Skip to content

i18n: translateFlow walks flow.nodes flat — a screen node inside an ADR-0031 region is never overlaid, in the fourth pass of a class three earlier cards each fixed once #11745

Description

@os-steve

Filed unassigned by the domain:devx seat while implementing #11608 (the packages/lintflows leg). packages/spec is outside that card's declared file surface, so this is its own card, not a rider.

Measured

packages/spec/src/system/i18n-resolver.ts, translateFlow:

constnodes=Array.isArray(flow.nodes)
? flow.nodes.map((node)=>{translateScreenNode(node,name,bundle,opts)})
: undefined;

It reads the flat flow.nodes array only. But FlowNode.config carries ADR-0031 regions — loop.config.body, parallel.config.branches[].nodes, try_catch.config.try / .catch, each holding a full nodes: z.array(FlowNodeSchema), nesting arbitrarily. A type: 'screen' node in any of them is a real screen: the executor pauses on it and the client receives its ScreenSpec.nodeId.

So for a nested screen, flows.<name>.screens.<node_id>.{title,fields.*} is authored, parses (the schema is keyed by node id and knows nothing about depth), and translateFlow walks straight past it. The wizard step renders its source-locale heading and field labels while the sibling screens one level up are translated — the same invisible hole the flows group was added to close (#7646).

Why this is worth a card rather than a note

This is the fourth pass in this repo to be written against the flat one-liner, and the first three were each filed and fixed separately:

cardpassoutcome
#4347applyConversionsToFlowconditions inside a loop never converted to CEL
#4380flow lint rulestwo gating errors silently inert, a third downgraded
#5383flow-inert-node-condition8 real inert conditions shipped

packages/spec/src/automation/region-slots.ts exists precisely so this fact has one declaration (FLOW_REGION_SLOTS), and its header names the four passes that need it. translateFlow is a fifth consumer that needs it and does not read it.

Note the asymmetry that makes this easy to miss: the client-side resolver path is already correct. resolveFlowScreenTitle / lookupFlowScreenCopy take a bare flowName + nodeId, so a paused nested screen resolves fine. Only the document-overlay path (translateFlow) is depth-blind — so whether this ships as a user-visible bug depends on which of the two the runner wiring picks, which #11287's docblock explicitly defers to a downstream card.

Shape of the fix

Walk regions via FLOW_REGION_SLOTS_BY_TYPE (or reuse the region-aware walk) inside translateFlow, preserving its reference-identity discipline: a node that resolved nothing must come back as the same reference, or nodesChanged starts reporting untouched documents as changed.

Not blocking #11608

The lint leg landing there collects its universe with walkFlowNodes, so it already resolves nested screens and does not report them as orphans — deliberately the safe direction for a warning. That means the lint rule and translateFlow currently disagree about which keys are live, and this card is what closes the gap.

Refs: #11608 · #11287 · #7646 · #4380 · #5383 · #4347

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions