From 80a825a0e8af47d17434b9db5da9f58eafa0a82f Mon Sep 17 00:00:00 2001 From: Roxana Mocanu Date: Thu, 22 Oct 2026 21:17:53 +0300 Subject: [PATCH 1/2] fix(apollo-react): replace ICU plurals in canvas counts with count-neutral labels Smartling cannot parse ICU plural syntax: it locks the branch text and leaves the syntax editable, and translations come back with the ICU keywords translated, which Lingui rejects and renders as raw markup. Rewrite the 15 canvas count strings (node variables panel, JSON tree, diff view summary, task runs tooltip) as "Label: {count}". Key IDs are unchanged, so the connector replaces the existing translations on the next sync. Only en.json is edited. Co-Authored-By: Claude Opus 5.5 --- .../CanvasDiffView/CanvasDiffView.test.tsx | 20 ++++++------- .../CanvasDiffView/CanvasDiffView.tsx | 14 ++++----- .../canvas/components/JsonTree/JsonTree.tsx | 4 +-- .../components/NodeIOView/NodeIOView.test.tsx | 16 +++++----- .../NodeVariablesPanel.test.tsx | 26 ++++++++-------- .../NodeVariablesPanel/NodeVariablesPanel.tsx | 12 ++++---- .../StageNode/tasks/TaskContent.test.tsx | 8 ++--- .../StageNode/tasks/TaskContent.tsx | 2 +- .../apollo-react/src/canvas/locales/en.json | 30 +++++++++---------- 9 files changed, 66 insertions(+), 66 deletions(-) diff --git a/packages/apollo-react/src/canvas/components/CanvasDiffView/CanvasDiffView.test.tsx b/packages/apollo-react/src/canvas/components/CanvasDiffView/CanvasDiffView.test.tsx index b4e8d80a56..1b93cf47ba 100644 --- a/packages/apollo-react/src/canvas/components/CanvasDiffView/CanvasDiffView.test.tsx +++ b/packages/apollo-react/src/canvas/components/CanvasDiffView/CanvasDiffView.test.tsx @@ -284,13 +284,13 @@ describe('CanvasDiffView', () => { const summary = screen.getByRole('list', { name: 'Change summary' }); const items = within(summary).getAllByRole('listitem'); expect(items.map((item) => item.textContent)).toEqual([ - '1 node added', - '1 node removed', - '1 node changed', - '1 connection removed', + 'Nodes added: 1', + 'Nodes removed: 1', + 'Nodes changed: 1', + 'Connections removed: 1', ]); - expect(screen.getByRole('status')).toHaveTextContent('1 node added'); - expect(screen.getByRole('status')).toHaveTextContent('1 connection removed'); + expect(screen.getByRole('status')).toHaveTextContent('Nodes added: 1'); + expect(screen.getByRole('status')).toHaveTextContent('Connections removed: 1'); }); it('reports an empty diff as no changes', () => { @@ -337,11 +337,11 @@ describe('CanvasDiffView', () => { within(screen.getByRole('list', { name })) .getAllByRole('listitem') .map((item) => item.textContent); - expect(items('Applied this turn')).toEqual(['1 node changed']); + expect(items('Applied this turn')).toEqual(['Nodes changed: 1']); expect(items('Pending approval')).toEqual([ - '1 node added', - '1 node removed', - '1 connection removed', + 'Nodes added: 1', + 'Nodes removed: 1', + 'Connections removed: 1', ]); expect(screen.queryByRole('list', { name: 'Change summary' })).not.toBeInTheDocument(); }); diff --git a/packages/apollo-react/src/canvas/components/CanvasDiffView/CanvasDiffView.tsx b/packages/apollo-react/src/canvas/components/CanvasDiffView/CanvasDiffView.tsx index 4446fa469d..a534af4624 100644 --- a/packages/apollo-react/src/canvas/components/CanvasDiffView/CanvasDiffView.tsx +++ b/packages/apollo-react/src/canvas/components/CanvasDiffView/CanvasDiffView.tsx @@ -49,7 +49,7 @@ interface SummaryItem { label: string; } -/** The non-zero counts of a summary as localized labels, e.g. "2 nodes added". */ +/** The non-zero counts of a summary as localized labels, e.g. "Nodes added: 2". */ function useSummaryItems(summary: DiffSummary): SummaryItem[] { const { _ } = useSafeLingui(); return useMemo(() => { @@ -59,7 +59,7 @@ function useSummaryItems(summary: DiffSummary): SummaryItem[] { kind: 'added', label: _({ id: 'canvas.diff_view.added_nodes', - message: '{count, plural, one {# node added} other {# nodes added}}', + message: 'Nodes added: {count}', values: { count: summary.addedNodes }, }), }, @@ -68,7 +68,7 @@ function useSummaryItems(summary: DiffSummary): SummaryItem[] { kind: 'removed', label: _({ id: 'canvas.diff_view.removed_nodes', - message: '{count, plural, one {# node removed} other {# nodes removed}}', + message: 'Nodes removed: {count}', values: { count: summary.removedNodes }, }), }, @@ -77,7 +77,7 @@ function useSummaryItems(summary: DiffSummary): SummaryItem[] { kind: 'changed', label: _({ id: 'canvas.diff_view.changed_nodes', - message: '{count, plural, one {# node changed} other {# nodes changed}}', + message: 'Nodes changed: {count}', values: { count: summary.changedNodes }, }), }, @@ -86,7 +86,7 @@ function useSummaryItems(summary: DiffSummary): SummaryItem[] { kind: 'added', label: _({ id: 'canvas.diff_view.added_edges', - message: '{count, plural, one {# connection added} other {# connections added}}', + message: 'Connections added: {count}', values: { count: summary.addedEdges }, }), }, @@ -95,7 +95,7 @@ function useSummaryItems(summary: DiffSummary): SummaryItem[] { kind: 'removed', label: _({ id: 'canvas.diff_view.removed_edges', - message: '{count, plural, one {# connection removed} other {# connections removed}}', + message: 'Connections removed: {count}', values: { count: summary.removedEdges }, }), }, @@ -104,7 +104,7 @@ function useSummaryItems(summary: DiffSummary): SummaryItem[] { kind: 'changed', label: _({ id: 'canvas.diff_view.changed_edges', - message: '{count, plural, one {# connection changed} other {# connections changed}}', + message: 'Connections changed: {count}', values: { count: summary.changedEdges }, }), }, diff --git a/packages/apollo-react/src/canvas/components/JsonTree/JsonTree.tsx b/packages/apollo-react/src/canvas/components/JsonTree/JsonTree.tsx index 6735fa559b..416f7bb902 100644 --- a/packages/apollo-react/src/canvas/components/JsonTree/JsonTree.tsx +++ b/packages/apollo-react/src/canvas/components/JsonTree/JsonTree.tsx @@ -54,13 +54,13 @@ export function useCanvasJsonTreeViewStrings(): JsonTreeViewStrings { itemCount: (count) => _({ id: 'canvas.json_value_panel.item_count', - message: '{count, plural, one {# item} other {# items}}', + message: 'Items: {count}', values: { count }, }), keyCount: (count) => _({ id: 'canvas.json_value_panel.key_count', - message: '{count, plural, one {# key} other {# keys}}', + message: 'Keys: {count}', values: { count }, }), requiredMarker: _({ id: 'canvas.json_value_panel.required_marker', message: 'required' }), diff --git a/packages/apollo-react/src/canvas/components/NodeIOView/NodeIOView.test.tsx b/packages/apollo-react/src/canvas/components/NodeIOView/NodeIOView.test.tsx index 6bf0ee7191..9608f8b81c 100644 --- a/packages/apollo-react/src/canvas/components/NodeIOView/NodeIOView.test.tsx +++ b/packages/apollo-react/src/canvas/components/NodeIOView/NodeIOView.test.tsx @@ -62,7 +62,7 @@ describe('NodeIOView', () => { name: 'Copy path for responseBody', }); // The count fills the cell right after the key's, not pushed to the far edge. - expect(key.closest('[role="gridcell"]')?.nextElementSibling?.textContent).toBe('1 key'); + expect(key.closest('[role="gridcell"]')?.nextElementSibling?.textContent).toBe('Keys: 1'); }); it('hides a container count when decoration.hideCount is set', () => { @@ -73,8 +73,8 @@ describe('NodeIOView', () => { /> ); // big's count is suppressed; the undecorated small container keeps its own. - expect(screen.queryByText('3 keys')).not.toBeInTheDocument(); - expect(screen.getByText('1 key')).toBeInTheDocument(); + expect(screen.queryByText('Keys: 3')).not.toBeInTheDocument(); + expect(screen.getByText('Keys: 1')).toBeInTheDocument(); }); it('renders the title bar when a title is given', () => { @@ -90,7 +90,7 @@ describe('NodeIOView', () => { const row = screen .getByRole('button', { name: 'Copy path for responseBody' }) .closest('[role="row"]') as HTMLElement; - await userEvent.click(within(row).getByText('1 key')); + await userEvent.click(within(row).getByText('Keys: 1')); expect(screen.queryByText('currency')).not.toBeInTheDocument(); }); @@ -792,8 +792,8 @@ describe('NodeIOView', () => { expect(screen.getByText('item')).toBeInTheDocument(); expect(screen.getByText('name')).toBeInTheDocument(); expect(screen.getByText('count')).toBeInTheDocument(); - // The preview is not a real element, so the array still reads "0 items". - expect(screen.getByText('0 items')).toBeInTheDocument(); + // The preview is not a real element, so the array still reads "Items: 0". + expect(screen.getByText('Items: 0')).toBeInTheDocument(); }); it('leaves the preview item read-only in an editable tree', () => { @@ -829,7 +829,7 @@ describe('NodeIOView', () => { /> ); expect(screen.getByText('item')).toBeInTheDocument(); - expect(screen.getByText('0 items')).toBeInTheDocument(); + expect(screen.getByText('Items: 0')).toBeInTheDocument(); }); it('previews the item shape even when the tree is read-only', () => { @@ -858,7 +858,7 @@ describe('NodeIOView', () => { /> ); expect(screen.queryByText('item')).not.toBeInTheDocument(); - expect(screen.getByText('2 items')).toBeInTheDocument(); + expect(screen.getByText('Items: 2')).toBeInTheDocument(); }); it('collapses row actions beyond the inline cap into an overflow menu', async () => { diff --git a/packages/apollo-react/src/canvas/components/NodeVariablesPanel/NodeVariablesPanel.test.tsx b/packages/apollo-react/src/canvas/components/NodeVariablesPanel/NodeVariablesPanel.test.tsx index 53da07a63b..89ad83ea3b 100644 --- a/packages/apollo-react/src/canvas/components/NodeVariablesPanel/NodeVariablesPanel.test.tsx +++ b/packages/apollo-react/src/canvas/components/NodeVariablesPanel/NodeVariablesPanel.test.tsx @@ -101,10 +101,10 @@ describe('NodeVariablesPanel', () => { it('counts each section and describes counts for assistive technology', () => { renderPanel(); - expect(screen.getByRole('img', { name: '3 inputs' })).toBeInTheDocument(); - expect(screen.getByRole('img', { name: '0 outputs' })).toBeInTheDocument(); - expect(screen.getByRole('img', { name: '3 variables' })).toBeInTheDocument(); - expect(screen.getByRole('img', { name: '1 node' })).toBeInTheDocument(); + expect(screen.getByRole('img', { name: 'Inputs: 3' })).toBeInTheDocument(); + expect(screen.getByRole('img', { name: 'Outputs: 0' })).toBeInTheDocument(); + expect(screen.getByRole('img', { name: 'Variables: 3' })).toBeInTheDocument(); + expect(screen.getByRole('img', { name: 'Nodes: 1' })).toBeInTheDocument(); }); it('shows each node by name and icon, with its id as the copied reference', async () => { @@ -139,9 +139,9 @@ describe('NodeVariablesPanel', () => { renderPanel({ defaultExpandedSections: ['inputs', 'nodes'] }); // The schema's top-level fields, or the consumer's outputCount. - expect(screen.getAllByRole('img', { name: '1 output' })).toHaveLength(3); + expect(screen.getAllByRole('img', { name: 'Outputs: 1' })).toHaveLength(3); // Manual trigger has none, like the empty Outputs section. - expect(screen.getAllByRole('img', { name: '0 outputs' })).toHaveLength(2); + expect(screen.getAllByRole('img', { name: 'Outputs: 0' })).toHaveLength(2); await user.click(screen.getByRole('button', { name: 'Expand messageReceivedInSlack1' })); await user.click(screen.getByRole('button', { name: 'Expand output' })); @@ -154,7 +154,7 @@ describe('NodeVariablesPanel', () => { renderPanel({ onFocusNode, onAdd: vi.fn() }); const focus = screen.getByRole('button', { name: 'Focus on Autonomous agent' }); - const count = within(section('Nodes')).getByRole('img', { name: '1 output' }); + const count = within(section('Nodes')).getByRole('img', { name: 'Outputs: 1' }); const add = screen.getByRole('button', { name: 'Add from Autonomous agent' }); // Count and + share the section header's right-edge columns. expect(focus.compareDocumentPosition(count) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy(); @@ -261,9 +261,9 @@ describe('NodeVariablesPanel', () => { ); expect(within(section('Inputs')).getByText('Webhook (upstream)')).toBeInTheDocument(); - expect(within(section('Inputs')).getByRole('img', { name: '2 outputs' })).toBeInTheDocument(); + expect(within(section('Inputs')).getByRole('img', { name: 'Outputs: 2' })).toBeInTheDocument(); expect(within(section('Nodes')).getByText('Webhook')).toBeInTheDocument(); - expect(within(section('Nodes')).getByRole('img', { name: '1 output' })).toBeInTheDocument(); + expect(within(section('Nodes')).getByRole('img', { name: 'Outputs: 1' })).toBeInTheDocument(); }); it('keeps expansion separate when the same node is in Inputs and Nodes', async () => { @@ -307,7 +307,7 @@ describe('NodeVariablesPanel', () => { /> ); - expect(screen.getByRole('img', { name: '0 outputs' })).toBeInTheDocument(); + expect(screen.getByRole('img', { name: 'Outputs: 0' })).toBeInTheDocument(); expect(within(section('Outputs')).getByText('item')).toBeInTheDocument(); }); @@ -358,8 +358,8 @@ describe('NodeVariablesPanel', () => { ); // The Outputs section and the List input node both have no real entries. - expect(screen.getAllByRole('img', { name: '0 outputs' })).toHaveLength(2); - expect(screen.getByRole('img', { name: '0 variables' })).toBeInTheDocument(); + expect(screen.getAllByRole('img', { name: 'Outputs: 0' })).toHaveLength(2); + expect(screen.getByRole('img', { name: 'Variables: 0' })).toBeInTheDocument(); }); it('edits a variable in a dialog, reading its details from the schema', async () => { @@ -1093,7 +1093,7 @@ describe('NodeVariablesPanel', () => { const names = screen.getAllByRole('region').map((region) => region.getAttribute('aria-label')); expect(names).toEqual(['Inputs', 'Outputs', 'Variables', 'Assets', 'Nodes']); expect(sectionToggle('Assets')).toHaveAttribute('aria-expanded', 'false'); - expect(screen.getByRole('img', { name: '2 assets' })).toBeInTheDocument(); + expect(screen.getByRole('img', { name: 'Assets: 2' })).toBeInTheDocument(); expect(screen.queryByText('invoiceApiKey')).not.toBeInTheDocument(); }); diff --git a/packages/apollo-react/src/canvas/components/NodeVariablesPanel/NodeVariablesPanel.tsx b/packages/apollo-react/src/canvas/components/NodeVariablesPanel/NodeVariablesPanel.tsx index a5545d1c95..289177912a 100644 --- a/packages/apollo-react/src/canvas/components/NodeVariablesPanel/NodeVariablesPanel.tsx +++ b/packages/apollo-react/src/canvas/components/NodeVariablesPanel/NodeVariablesPanel.tsx @@ -479,7 +479,7 @@ export function NodeVariablesPanel({ const count = source.outputCount ?? countEntries(node.children); const countLabel = _({ id: 'canvas.node_variables_panel.node_output_count', - message: '{count, plural, one {# output} other {# outputs}}', + message: 'Outputs: {count}', values: { count }, }); return { @@ -709,27 +709,27 @@ export function NodeVariablesPanel({ ({ inputs: _({ id: 'canvas.node_variables_panel.count_inputs', - message: '{count, plural, one {# input} other {# inputs}}', + message: 'Inputs: {count}', values: { count }, }), outputs: _({ id: 'canvas.node_variables_panel.count_outputs', - message: '{count, plural, one {# output} other {# outputs}}', + message: 'Outputs: {count}', values: { count }, }), variables: _({ id: 'canvas.node_variables_panel.count_variables', - message: '{count, plural, one {# variable} other {# variables}}', + message: 'Variables: {count}', values: { count }, }), assets: _({ id: 'canvas.node_variables_panel.count_assets', - message: '{count, plural, one {# asset} other {# assets}}', + message: 'Assets: {count}', values: { count }, }), nodes: _({ id: 'canvas.node_variables_panel.count_nodes', - message: '{count, plural, one {# node} other {# nodes}}', + message: 'Nodes: {count}', values: { count }, }), })[section]; diff --git a/packages/apollo-react/src/canvas/components/StageNode/tasks/TaskContent.test.tsx b/packages/apollo-react/src/canvas/components/StageNode/tasks/TaskContent.test.tsx index 22c6b6016a..901266e69b 100644 --- a/packages/apollo-react/src/canvas/components/StageNode/tasks/TaskContent.test.tsx +++ b/packages/apollo-react/src/canvas/components/StageNode/tasks/TaskContent.test.tsx @@ -107,7 +107,7 @@ describe('TaskContent - runs chip', () => { const chip = screen.getByText('3'); expect(chip.closest('[data-testid="canvas-tooltip"]')).toHaveAttribute( 'data-tooltip-content', - 'Ran 3 times' + 'Runs: 3' ); }); @@ -122,14 +122,14 @@ describe('TaskContent - runs chip', () => { ); }); - it('interpolates the total-run count into the plural tooltip form', () => { + it('interpolates the total-run count into the runs tooltip', () => { renderTaskContent({ taskExecution: { status: 'Failed', duration: '1m', retryCount: 3 }, }); const chip = screen.getByText('4'); expect(chip.closest('[data-testid="canvas-tooltip"]')).toHaveAttribute( 'data-tooltip-content', - 'Ran 4 times' + 'Runs: 4' ); }); @@ -140,7 +140,7 @@ describe('TaskContent - runs chip', () => { const chip = screen.getByText('2'); expect(chip.closest('[data-testid="canvas-tooltip"]')).toHaveAttribute( 'data-tooltip-content', - 'Ran 2 times' + 'Runs: 2' ); }); diff --git a/packages/apollo-react/src/canvas/components/StageNode/tasks/TaskContent.tsx b/packages/apollo-react/src/canvas/components/StageNode/tasks/TaskContent.tsx index 20517d6c88..87c045636d 100644 --- a/packages/apollo-react/src/canvas/components/StageNode/tasks/TaskContent.tsx +++ b/packages/apollo-react/src/canvas/components/StageNode/tasks/TaskContent.tsx @@ -108,7 +108,7 @@ export const TaskContent = memo( ? _({ id: 'stage-node.task-badge.running-again', message: 'Running again' }) : _({ id: 'stage-node.task-badge.ran-n-times', - message: '{count, plural, one {Ran # time} other {Ran # times}}', + message: 'Runs: {count}', values: { count: totalRuns }, }); const hasRework = !!taskExecution?.retryDuration; diff --git a/packages/apollo-react/src/canvas/locales/en.json b/packages/apollo-react/src/canvas/locales/en.json index 555fcd6425..0b98715a19 100644 --- a/packages/apollo-react/src/canvas/locales/en.json +++ b/packages/apollo-react/src/canvas/locales/en.json @@ -1,19 +1,19 @@ { "add-node-panel.search-nodes": "Search nodes", - "canvas.diff_view.added_edges": "{count, plural, one {# connection added} other {# connections added}}", - "canvas.diff_view.added_nodes": "{count, plural, one {# node added} other {# nodes added}}", + "canvas.diff_view.added_edges": "Connections added: {count}", + "canvas.diff_view.added_nodes": "Nodes added: {count}", "canvas.diff_view.after_label": "After", "canvas.diff_view.before_label": "Before", - "canvas.diff_view.changed_edges": "{count, plural, one {# connection changed} other {# connections changed}}", - "canvas.diff_view.changed_nodes": "{count, plural, one {# node changed} other {# nodes changed}}", + "canvas.diff_view.changed_edges": "Connections changed: {count}", + "canvas.diff_view.changed_nodes": "Nodes changed: {count}", "canvas.diff_view.code_line_added": "Added", "canvas.diff_view.code_line_removed": "Removed", "canvas.diff_view.code_too_large": "Too many changes to highlight. Showing both versions unmarked.", "canvas.diff_view.code_view": "Code diff", "canvas.diff_view.keep": "Keep", "canvas.diff_view.no_changes": "No changes", - "canvas.diff_view.removed_edges": "{count, plural, one {# connection removed} other {# connections removed}}", - "canvas.diff_view.removed_nodes": "{count, plural, one {# node removed} other {# nodes removed}}", + "canvas.diff_view.removed_edges": "Connections removed: {count}", + "canvas.diff_view.removed_nodes": "Nodes removed: {count}", "canvas.diff_view.revert": "Revert", "canvas.diff_view.show_side_by_side": "Show side by side", "canvas.diff_view.show_stacked": "Show stacked", @@ -46,9 +46,9 @@ "canvas.json_value_panel.filter_active": "Filter: {label}", "canvas.json_value_panel.filter_all": "All", "canvas.json_value_panel.invalid_json": "Invalid JSON", - "canvas.json_value_panel.item_count": "{count, plural, one {# item} other {# items}}", + "canvas.json_value_panel.item_count": "Items: {count}", "canvas.json_value_panel.json_tab": "JSON", - "canvas.json_value_panel.key_count": "{count, plural, one {# key} other {# keys}}", + "canvas.json_value_panel.key_count": "Keys: {count}", "canvas.json_value_panel.path_copied": "Path copied", "canvas.json_value_panel.reference_type": "{typeLabel} ยท reference", "canvas.json_value_panel.required_marker": "required", @@ -82,11 +82,11 @@ "canvas.node_variables_panel.add_to_node": "Add", "canvas.node_variables_panel.add_to_node_label": "Add from {label}", "canvas.node_variables_panel.add_variables": "Add variables", - "canvas.node_variables_panel.count_assets": "{count, plural, one {# asset} other {# assets}}", - "canvas.node_variables_panel.count_inputs": "{count, plural, one {# input} other {# inputs}}", - "canvas.node_variables_panel.count_nodes": "{count, plural, one {# node} other {# nodes}}", - "canvas.node_variables_panel.count_outputs": "{count, plural, one {# output} other {# outputs}}", - "canvas.node_variables_panel.count_variables": "{count, plural, one {# variable} other {# variables}}", + "canvas.node_variables_panel.count_assets": "Assets: {count}", + "canvas.node_variables_panel.count_inputs": "Inputs: {count}", + "canvas.node_variables_panel.count_nodes": "Nodes: {count}", + "canvas.node_variables_panel.count_outputs": "Outputs: {count}", + "canvas.node_variables_panel.count_variables": "Variables: {count}", "canvas.node_variables_panel.delete_variable": "Delete {name}", "canvas.node_variables_panel.edit_cancel": "Cancel", "canvas.node_variables_panel.edit_default_array_error": "Enter a valid JSON array.", @@ -113,7 +113,7 @@ "canvas.node_variables_panel.focus_node": "Focus on node", "canvas.node_variables_panel.focus_node_label": "Focus on {label}", "canvas.node_variables_panel.no_matches": "No variables match your search.", - "canvas.node_variables_panel.node_output_count": "{count, plural, one {# output} other {# outputs}}", + "canvas.node_variables_panel.node_output_count": "Outputs: {count}", "canvas.node_variables_panel.search_placeholder": "Search variables", "canvas.node_variables_panel.section_assets": "Assets", "canvas.node_variables_panel.section_inputs": "Inputs", @@ -677,7 +677,7 @@ "stage-node.status.paused": "Paused", "stage-node.status.terminated": "Terminated", "stage-node.status.warning": "Warning", - "stage-node.task-badge.ran-n-times": "{count, plural, one {Ran # time} other {Ran # times}}", + "stage-node.task-badge.ran-n-times": "Runs: {count}", "stage-node.task-badge.running-again": "Running again", "stage-node.task-mocked": "This task's outputs are mocked", "stage-node.ungroup-parallel-tasks": "Ungroup parallel tasks", From 59784a011bc25802d53ecfee057863a21e75371f Mon Sep 17 00:00:00 2001 From: Roxana Mocanu Date: Thu, 22 Oct 2026 22:10:38 +0300 Subject: [PATCH 2/2] fix(apollo-react): replace ICU plurals in Wind chat JSON counts with count-neutral labels The Wind tool-call JSON tree counts used Lingui plural(), which emits ICU plural syntax the TMS cannot parse. Use the same "Label: {count}" pattern as the canvas JSON tree: "Items: {count}" and "Keys: {count}". The keys have not reached other locales yet, so only en.json changes. Co-Authored-By: Claude Opus 5.5 --- packages/apollo-react/src/chat/locales/en.json | 4 ++-- .../src/chat/wind/renderers/tool-call-strings.ts | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/apollo-react/src/chat/locales/en.json b/packages/apollo-react/src/chat/locales/en.json index 73f6cc7744..e2c3fa3af7 100644 --- a/packages/apollo-react/src/chat/locales/en.json +++ b/packages/apollo-react/src/chat/locales/en.json @@ -106,8 +106,8 @@ "autopilot-chat.tool-call.json.copy-value": "Copy value", "autopilot-chat.tool-call.json.copy-value-of": "Copy value of {key}", "autopilot-chat.tool-call.json.expand": "Expand {key}", - "autopilot-chat.tool-call.json.item-count": "{count, plural, one {# item} other {# items}}", - "autopilot-chat.tool-call.json.key-count": "{count, plural, one {# key} other {# keys}}", + "autopilot-chat.tool-call.json.item-count": "Items: {count}", + "autopilot-chat.tool-call.json.key-count": "Keys: {count}", "autopilot-chat.tool-call.json.path-copied": "Path copied", "autopilot-chat.tool-call.output": "Output", "autopilot-chat.tool-call.pending": "Pending", diff --git a/packages/apollo-react/src/chat/wind/renderers/tool-call-strings.ts b/packages/apollo-react/src/chat/wind/renderers/tool-call-strings.ts index 14061ee9eb..69c2d5e3a6 100644 --- a/packages/apollo-react/src/chat/wind/renderers/tool-call-strings.ts +++ b/packages/apollo-react/src/chat/wind/renderers/tool-call-strings.ts @@ -1,4 +1,4 @@ -import { msg, plural } from '@lingui/core/macro'; +import { msg } from '@lingui/core/macro'; import { useLingui } from '@lingui/react'; import type { JsonTreeViewStrings, ToolCallStrings } from '@uipath/apollo-wind'; import React from 'react'; @@ -46,14 +46,14 @@ export function useToolCallStrings(): WindToolCallStrings { _( msg({ id: 'autopilot-chat.tool-call.json.item-count', - message: plural(count, { one: '# item', other: '# items' }), + message: `Items: ${count}`, }) ), keyCount: (count) => _( msg({ id: 'autopilot-chat.tool-call.json.key-count', - message: plural(count, { one: '# key', other: '# keys' }), + message: `Keys: ${count}`, }) ), pathCopied: _(