Hermes 284313f908
Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Initial import of AlgaPSA codebase from PSA server
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz

Source: /opt/alga-psa on psa.joliet.tech
2026-06-22 16:12:17 -05:00

18 KiB
Raw Blame History

PRD — MSP Workflows i18n (Batch 2b-9)

Status: Draft Owner: i18n working group Started: 2026-04-18 Plan reference: .ai/translation/MSP_i18n_plan.md → Batch 2b-9 Predecessors: Batches 2b-1 through 2b-8, 2b-10 through 2b-21 already shipped. Workflows is the last large MSP surface with zero useTranslation wiring.


Problem statement

The MSP Workflows UI (workflow definitions list, run studio, designer, task inbox) has zero i18n wiring. All user-visible strings are hardcoded English in components under:

  • ee/server/src/components/workflow-designer/ (34 non-test .tsx files + helpers)
  • ee/server/src/components/workflow-graph/ (1 .tsx)
  • ee/server/src/components/workflow-run-studio/ (1 .tsx)
  • ee/packages/workflows/src/components/workflow/ (12 .tsx, task + form UI)

No msp/workflows.json namespace exists in server/public/locales/*/. The MSP feature flag msp-i18n-enabled is shipped to all non-English users; on any workflow route they see mixed English/translated UI because the shell is translated but the workflow surface is not.

Beyond string extraction, the designer and run list contain ~13 distinct inline option arrays with English labels (workflow run status, step status, log level, AI schema type, reference mode, wait mode, etc.). These are the enum-labels-pattern.md anti-pattern: they never reach t(), so validation passes with 0 missing keys while the UI renders English in every locale.

User value

  • Non-English MSP operators can build, run, and debug workflows in their preferred language (fr, es, de, nl, it, pl).
  • Pseudo-locale QA can actually catch regressions in the workflow surface (currently every string shows as English, masking real gaps).
  • Removes the single largest remaining hole in MSP coverage, unlocking Phase 7 rollout / flag removal.

Goals

  1. Create msp/workflows namespace with translations in en/fr/es/de/nl/it/pl + pseudo-locales xx/yy.
  2. Extract every user-visible string in the 48 in-scope components to t() calls.
  3. Migrate every inline { value, label } option array and any *_DISPLAY / *_OPTIONS constants to the enum-labels option-hook pattern: VALUES + LABEL_DEFAULTS in a constants file, useXOptions() / useFormatX() hooks colocated in ee/packages/workflows/src/hooks/.
  4. Register /msp/workflows, /msp/workflows/runs, /msp/workflows/runs/[runId], /msp/workflow-editor, /msp/workflow-editor/[workflowId], /msp/workflow-editor/new, and /msp/workflow-control in ROUTE_NAMESPACES.
  5. Migrate hardcoded date/number/duration formatting to useFormatters() (the designer renders timestamps in the run list, event list, and step history; all are toLocaleString-flavored today).
  6. Pass node scripts/validate-translations.cjs with zero missing/extra keys across 9 locales.
  7. Visual QA on xx: every user-visible string in the workflow surface shows 11111. No English bleed-through.
  8. CI workflow validation passes on PR.

Non-goals

  • Registry metadata translation. Action display names, descriptions, and schema field labels come from /api/workflow/registry/* endpoints, which return whatever the action definition module declared. Those labels are data, not UI chrome, and belong in a separate "workflow action catalog i18n" initiative (not scoped here).
  • User-authored workflow content. Workflow names, step names, comments, etc. are tenant data and never translated.
  • Temporal / worker / engine logs. Server-side log lines, audit payloads, and engine error messages below the UI layer are out of scope. Only strings rendered to the operator in the run studio / dead-letter view are in scope.
  • Workflow API error responses. Server actions keep returning English { success, error } payloads; components map them to translation keys via the standard error-map pattern (see translation-guide.md#error-and-validation-translation-pattern).
  • FormExample.tsx, TaskInboxExample.tsx, ConditionalFormExample.tsx. These are demo/example components not mounted in production routes. Extract only if trivially colocated; otherwise defer with a comment.
  • Monaco editor chrome (JSONata expression editor). The Monaco toolbar/status messages are vendor UI; only the surrounding wrapper (hints, validation badges, placeholder text) is in scope.
  • Workflow graph React Flow node internals. Node headers/badges that come from registry metadata are data; only static chrome (empty state, toolbar buttons) is in scope.

Target users and primary flows

  • MSP workflow author — opens /msp/workflow-editor/new or [workflowId], drags actions from the palette, configures inputs, sets triggers, saves.
  • MSP workflow operator — visits /msp/workflows/runs, filters by status/date, opens a run to inspect steps and logs, retries or cancels.
  • MSP admin — inspects dead-letter queue, views audit log, manages schedules.
  • Internal task assignee — receives a workflow-generated task, opens it from the inbox, completes the form.

Every flow must render fully localized in fr/es/de/nl/it/pl. German/Dutch layout risk is highest in the run list table (long status labels) — verify no truncation regressions.

Enum migration — explicit scope

All the following must move from inline arrays in components to the option-hook pattern. Each lives in ee/packages/workflows/src/constants/workflowEnums.ts (values + label defaults) with hooks in ee/packages/workflows/src/hooks/useWorkflowEnumOptions.ts. Translation keys go under enums.<enumName>.* in msp/workflows.json.

Enum Values Source files today Target key root
workflowRunStatus RUNNING, WAITING, SUCCEEDED, FAILED, CANCELED WorkflowRunList.tsx:87-91 enums.workflowRunStatus.*
workflowRunSort started_at:desc/asc, updated_at:desc/asc WorkflowRunList.tsx:95-98 enums.workflowRunSort.*
workflowEventStatus matched, unmatched, error WorkflowEventList.tsx:74-76 enums.workflowEventStatus.*
workflowStepStatus STARTED, SUCCEEDED, FAILED, RETRY_SCHEDULED, CANCELED WorkflowRunDetails.tsx:176-180 enums.workflowStepStatus.*
workflowLogLevel DEBUG, INFO, WARN, ERROR WorkflowRunDetails.tsx:185-188 enums.workflowLogLevel.*
workflowAiSchemaType string, number, integer, boolean, object, array WorkflowAiSchemaSection.tsx:51-64 (x2 copies) enums.workflowAiSchemaType.*
workflowInputSourceMode reference, fixed WorkflowActionInputSourceMode.tsx:27-28 enums.workflowInputSourceMode.*
workflowReferenceSection payload, vars, meta, error, forEach workflowReferenceSelector.tsx:399-403 enums.workflowReferenceSection.*
workflowTriggerMode manual, event WorkflowDesigner.tsx:3713-3714 enums.workflowTriggerMode.*
workflowCanvasView list, graph WorkflowDesigner.tsx:4724-4725 enums.workflowCanvasView.*
workflowOnError continue, fail WorkflowDesigner.tsx:6249-6250 enums.workflowOnError.*
workflowWaitMode duration, until WorkflowDesigner.tsx:6550-6551 enums.workflowWaitMode.*
workflowWaitTiming fixed, expression WorkflowDesigner.tsx:6614-6615 enums.workflowWaitTiming.*

Filter sentinels (e.g. { value: 'all', label: 'All statuses' }) are not enum values — they stay as plain t('filters.allStatuses') / t('filters.allLevels') / t('filters.allTypes') calls at each call site. Hooks return the real enum options only; the "All X" row is prepended in the component.

Activity / task status (getActivityStatusOptions in ee/packages/workflows/src/actions/activity-actions/activityStatusActions.ts) is a server action that returns { value, label } pairs from DB rows. Those labels are data, not UI chrome — flag in enum-labels-pattern.md's backlog but do not migrate here. If any consumer in scope renders those labels, add a TODO and keep existing behavior.

Reviewer enforcement: No PR in this batch adds a new *_DISPLAY / *_LABELS / *_OPTIONS map. No PR leaves an inline { value, label: 'CapitalizedEnglish' } array in a touched component. The enum-labels grep in enum-labels-pattern.md#finding-latent-gaps must return zero new matches in the workflow directories after each sub-batch merges.

Namespace + route config

  • Namespace: msp/workflows — single namespace for the entire workflow surface (designer, runs, dead-letter, schedules, task inbox, audit, graph, run-studio shell).
  • Rationale for single namespace: blast radius is one MSP feature area; no shared consumers in client portal; key count estimate (~1,0001,500 after all sub-batches) stays well under the msp/clients precedent of 953.
  • ROUTE_NAMESPACES additions (in packages/core/src/lib/i18n/config.ts):
    • /msp/workflows: ['common', 'msp/core', 'msp/workflows']
    • /msp/workflows/runs: same
    • /msp/workflow-editor: same
    • /msp/workflow-control: same
  • Prefix-match in getNamespacesForRoute() covers /msp/workflows/runs/[runId], /msp/workflow-editor/[workflowId], /msp/workflow-editor/new without explicit entries.

Acceptance criteria (definition of done)

  • server/public/locales/{en,fr,es,de,nl,it,pl,xx,yy}/msp/workflows.json all exist; key counts match exactly across all 9 locales.
  • node scripts/validate-translations.cjs passes with zero missing or extra keys.
  • No useTranslation() call in any in-scope component targets a namespace other than msp/workflows, common, or msp/core.
  • The following greps (run against ee/server/src/components/workflow-designer, ee/server/src/components/workflow-graph, ee/server/src/components/workflow-run-studio, ee/packages/workflows/src/components/workflow) return zero matches:
    • \{\s*value:\s*['"][^'"]+['"]\s*,\s*label:\s*['"][A-Z][^'"]*['"] (inline English option arrays)
    • ^export const [A-Z_]+(_DISPLAY|_LABELS|_OPTIONS|_MAP)\s*[:=] (label-map exports)
    • toLocaleDateString\(['"]en / toLocaleString\(['"]en (hardcoded locale formatting)
  • Visual QA on xx locale: every user-visible string in /msp/workflows, /msp/workflows/runs, /msp/workflows/runs/<runId>, /msp/workflow-editor/new, /msp/workflow-editor/<workflowId>, /msp/workflow-control, and the task inbox renders 11111 — zero English leakage.
  • Italian accent audit grep clean on locales/it/msp/workflows.json (see translation-guide.md#common-pitfalls rule 12).
  • Translated section / tab names in msp/workflows.json match canonical names in msp/core.json for every language (e.g. German "Workflows" or "Arbeitsabläufe" must match whichever core.json uses).
  • ContractLinesSubbatch.i18n.test.ts-style audit test added for the workflows surface (optional but recommended — covers regressions when new strings are added).
  • CI .github/workflows/validate-translations.yml green.
  • No component regressions in existing workflow Vitest suites (34 test files in ee/server/src/components/workflow-designer/__tests__/) — tests may need small updates if they assert English strings directly; prefer asserting on structural semantics (buttons by role, option values not labels).

Risks

  1. WorkflowDesigner.tsx is ~7,000 lines. Extracting strings in one pass is high-risk. Mitigation: sub-batch WF-C treats this file as its own unit; rely on the enum hooks landing in WF-A so most inline arrays are already gone.
  2. Backend status codes. Step/run/event status enum values come from the server (Temporal activity names, DB status columns). Hook translation keys use the raw enum value (e.g. SUCCEEDED) as the key segment — do not normalize to lowercase. A server-side status rename becomes a missing-key defect, not a mis-translation; the default-value fallback keeps the UI functional.
  3. React Flow node labels. WorkflowGraph.tsx renders nodes whose labels are computed from the workflow definition. Distinguish: static node chrome (empty state, mini-map labels, zoom controls) is in scope; node body text driven by workflow data is out of scope.
  4. Vitest snapshot / text-match breakage. Several __tests__/*.test.tsx suites assert on English labels (e.g. getByText('Running')). When migrating a status from inline array to useFormatWorkflowRunStatus, the test must switch to getByRole or a data-testid, not rely on the English label. Budget extra time in each sub-batch for test updates.
  5. Expression editor / Monaco. Custom hover/completion messages may have embedded English. Confirm before WF-D starts whether these are user-facing or dev-only; defer if vendor-owned.
  6. Dead-letter toast copy. Several server actions under workflowScheduleServerActions.ts and dead-letter reroute flows produce toasts with English text. The pattern for server-action error strings is to map them in the component (already established in translation-guide.md#error-and-validation-translation-pattern) — do not translate server strings.
  7. German layout. Run list table columns may get wider; spot-check in WF-B QA.
  8. Feature flag interaction. msp-i18n-enabled is currently rolled out widely. When the flag is off, I18nWrapper forces locale=en and the component renders the defaultValue from each t() call. Every t() must supply defaultValue to match the English copy today, otherwise flag-off users see the key string.

Rollout

  • No data migrations; pure UI batch.
  • No feature flag changes. msp-i18n-enabled already gates MSP i18n broadly; this batch simply widens coverage under the existing flag.
  • Ship each sub-batch as its own PR against main (or intermediate branch per Alga convention). After WF-F ships, close batch 2b-9 in MSP_i18n_plan.md.

Sub-batch plan

Sub-batch Scope Est. keys added PR surface
WF-A Namespace bootstrap + enum foundation. Create msp/workflows.json (English), add workflowEnums.ts constants, publish useWorkflowEnumOptions.ts hooks, add ROUTE_NAMESPACES entries, generate empty/structural keys, regenerate pseudo-locales, run validation. No component wiring yet except replacing inline enum arrays with hook calls where they already exist. ~100 (enum keys × 13 enums, approximate) Infra only
WF-B Run studio surface. Wire WorkflowRunList, WorkflowRunDetails, WorkflowRunDialog, WorkflowEventList, WorkflowDeadLetterQueue, WorkflowSchedules, WorkflowDefinitionAudit, RunStudioShell, WorkflowGraph, workflowRunDialogUtils, workflowRunTriggerPresentation, workflowActionPresentation. Migrate hardcoded timestamps to useFormatters. ~250 12 files
WF-C Designer shell — WorkflowDesigner.tsx + WorkflowDesignerPalette.tsx + PaletteItemWithTooltip.tsx. This is the biggest single file in the batch. Depends on WF-A enum hooks being merged. ~400 3 files (but one is 7k lines)
WF-D Designer editors + mapping. Wire ActionSchemaReference, GroupedActionConfigSection, InputMappingEditor, MappingPanel, SourceDataTree, ValidationBadge, MappingEditorSkeleton, MappingConnectionsOverlay, ExpressionEditor, ExpressionEditorField, ExpressionTextArea, ExpressionAutocomplete, PipelineComponents, WorkflowActionInputFieldInfo, WorkflowActionInputFixedPicker, WorkflowActionInputSection, WorkflowActionInputSourceMode, WorkflowActionInputTypeHint, WorkflowAiSchemaSection, WorkflowComposeTextDocumentEditor, WorkflowComposeTextSection, WorkflowStepNameField, WorkflowStepSaveOutputSection, WorkflowWaitEditors, workflowReferenceSelector. ~300 ~22 files
WF-E Task + form components in ee/packages/workflows/src/components/workflow/: TaskInbox, TaskList, TaskDetails, TaskForm, TaskHistory, EmbeddedTaskInbox, DynamicForm, ActionButton, ActionButtonGroup. Example/demo components deferred. ~150 9 files
WF-F AI-generate translations for fr/es/de/nl/it/pl. Regenerate pseudo-locales. Italian accent audit. Cross-check against msp/core.json. Run validate-translations.cjs. Visual QA on xx across all workflow routes. Fix missed strings. Update MSP_i18n_plan.md marking 2b-9 complete. Add Workflows rows to the component-coverage tables. 0 (translates existing keys) Locale files only

Target execution cadence: WF-A first (blocking), then WF-B + WF-E in parallel, then WF-C, then WF-D, then WF-F.

Open questions

  1. Do we want a separate msp/workflow-tasks namespace for the task inbox components, so the task UI can be reused in client portal later without loading the full workflow designer namespace? Tentative answer: No — keep single msp/workflows namespace; if client portal ever needs tasks, split then. Documented in SCRATCHPAD.
  2. Should RunStudioShell live in its own namespace (it's a distinct route surface)? Tentative answer: No — the run studio is a mode of the workflow surface, single namespace is fine. Loaded on the same route family.
  3. Are there existing workflow-related translation keys in common.json we should reuse (e.g. status.*, actions.save)? Answer: Yes — reuse common.json for generic verbs/nouns (Save, Cancel, Delete, Back) and for generic statuses like active, inactive. Use msp/workflows for workflow-specific statuses (RUNNING, WAITING, etc.). Never duplicate.
  4. Do we need a features/workflow-tasks cross-portal namespace if the task form package is eventually consumed by MSP + client portal? Deferred — re-evaluate only if/when client portal ever renders a workflow task. Today it doesn't.
  • Full batch plan: .ai/translation/MSP_i18n_plan.md
  • Translation workflow guide: .ai/translation/translation-guide.md
  • Enum migration pattern: .ai/translation/enum-labels-pattern.md
  • Example hook style: packages/billing/src/hooks/useBillingEnumOptions.ts
  • Precedent enum backlog entries (Assets, Quotes, Invoices, Add-ons, KB): enum-labels-pattern.md#non-billing-backlog