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

53 KiB
Raw Permalink Blame History

Scratchpad — MSP i18n Batch 2b-21b/c: Projects + Project Templates Migration

  • Plan slug: 2026-04-05-msp-i18n-projects-migration
  • Created: 2026-04-05
  • Status: COMPLETE (2026-04-08)

What This Is

Mechanical wiring pass: ~60 unwired MSP project + project-template components × useTranslation(['features/projects', 'common']). Shared namespace (128 keys, 9 locales) already exists and is already loaded by ROUTE_NAMESPACES['/msp/projects']. The templates.* subtree (17 keys) is live. Client-portal side is 100% wired (9/9) — this closes the MSP gap.

Consolidates parent plan's 2b-21b (projects, 45 files) and 2b-21c (project-templates, 15 files) because they share package, namespace, and patterns.

Decisions

  • (2026-04-05) Keep project-templates strings in existing features/projects.json under templates.* subtree rather than creating a new features/project-templates.json namespace. Rationale: already-wired components (TemplateStatusColumnsStep, TemplateStatusManager, ProjectTaskStatusSettings) use features/projects with templates.* keys and it's working. Splitting would fragment the namespace needlessly. Supersedes the parent plan's tentative "new features/project-templates namespace" idea.
  • (2026-04-05) Use array multi-namespace form: useTranslation(['features/projects', 'common']). Matches all 5 already-wired components. Generic actions (Save/Cancel/Delete/Confirm) pull from common via common: prefix.
  • (2026-04-05) Ship sub-batches A (projects core, 15 files), B (project-templates, 15 files), C (settings + small, 30 files) as independent PRs.
  • (2026-04-05) Template wizard strings nested per-step (templates.wizard.basics.*, templates.wizard.tasks.*, etc.) rather than flat. Matches existing onboarding wizard pattern in msp/onboarding.json.
  • (2026-04-05) Task document strings — prefer reusing features/documents keys where they match (upload/download/attach/remove). Only add features/projects.taskDocuments.* for project-task-specific copy. Verify features/documents is loaded transitively on /msp/projects/[id] routes.
  • (2026-04-05) Translate toast messages, inline validation, and user-visible error strings. Do NOT translate throw new Error('...') strings caught by error boundaries or logged only.

Discoveries / Constraints

  • (2026-04-05) features/projects.json top-level groups: title, subtitle, searchPlaceholder, allStatuses, resetFilters, active, completed, onHold, timeline, milestones, phasesAndTasks, kanbanView, listView, task, tasks (15), phases (7), settings (22), templates (17), documents (13), team, budget, fields (12), status (5), messages (5), backToProjects, invalidProjectData, plus ~16 leaf fields. Total: 128 keys.
  • (2026-04-05) ROUTE_NAMESPACES entries that load features/projects:
    • /client-portal/projects — already works
    • /msp/projects — loads ['common', 'msp/core', 'features/projects']
    • /msp/settings — loads ['common', 'msp/core', 'msp/settings', 'msp/admin', 'msp/email-providers', 'features/projects'] (already includes it!)
    • /msp/billing — loads ['common', 'msp/core', 'features/billing', 'msp/reports'] (does not include projects — if any project component is rendered on billing, fix needed)
  • (2026-04-05) Templates routes NOT in ROUTE_NAMESPACES:
    • /msp/projects/templates
    • /msp/projects/templates/[templateId]
    • /msp/projects/templates/create Should match-best against /msp/projects and inherit its namespaces. Verify.
  • (2026-04-05) Already-wired MSP project components (reference patterns):
    • PhaseListItem.tsxuseTranslation('features/projects') (single-namespace form)
    • TemplateStatusManager.tsxuseTranslation(['features/projects', 'common']) (array form)
    • TemplateStatusColumnsStep.tsxuseTranslation(['features/projects', 'common'])
    • ProjectTaskStatusSettings.tsxuseTranslation(['features/projects', 'common'])
    • TaskComment.tsxuseTranslation('common') Preferred: array form — matches 3 of 5, supports common: prefix for shared keys.
  • (2026-04-05) Largest files dominate the string count:
    • ProjectDetail.tsx 3,038 LOC / ~50 strings
    • TemplateEditor.tsx 2,315 LOC / ~28 strings
    • TaskForm.tsx 2,024 LOC / ~39 strings
    • TaskListView.tsx 1,320 LOC / ~9 strings
    • PhaseTaskImportDialog.tsx 1,290 LOC / ~21 strings
    • TemplateTaskForm.tsx 1,015 LOC / ~22 strings
    • Projects.tsx 980 LOC / ~21 strings
    • TemplateTaskListView.tsx 953 LOC / ~8 strings These 8 files alone are ~12,900 LOC and ~198 strings of the estimated ~450 total.
  • (2026-04-05) Rough string estimates (heuristic undercount):
    • Sub-batch A (projects core): 15 files, ~250 strings
    • Sub-batch B (project-templates): 15 files, ~150 strings
    • Sub-batch C (settings + small): 30 files, ~80-100 strings
    • Total: ~480 strings (realistic: 500-650)
  • (2026-04-05) ProjectQuickAdd reused in global quick-create: server/src/components/layout/QuickCreateDialog.tsx. Must work in both contexts.
  • (2026-04-05) Zero-string components to verify: StatusColumn, TaskCommentThread, KanbanBoard, ClientPortalConfigEditor, ProjectPage, KanbanZoomControl, TaskCommentForm, DonutChart, TaskQuickAdd, TaskEdit, HoursProgressBar, ProjectActiveToggle, TaskPrioritySettings. Most are layout/style-only or re-export shims.
  • (2026-04-05) ProjectSettings is exported from @alga-psa/projects/components and imported by server/src/components/settings/SettingsPage.tsx (Settings page wiring).
  • (2026-04-07, F001 audit) Existing features/projects keys are enough for: base projects list chrome (title, subtitle, searchPlaceholder, allStatuses, resetFilters), generic project fields (fields.*), summary cards (taskCompletion, budgetHours, hoursUsage, etc.), base task table headers (tasks.*), phase shell (phases.*), attachments shell (documents.*), template status-column management (templates.statuses.*), and project/phase status settings (settings.statuses.*).
  • (2026-04-07, F001 audit) Confirmed missing MSP key groups for sub-batch A: projectList.* (filters, empty-state CTAs, row actions, deletion toasts), quickAdd.*, projectDetail.* (header actions, tabs, metrics, phase actions, search), taskForm.* (field labels/placeholders, validation, checklist/deletion/move/duplicate confirmations, prefill copy), taskDependencies.*, taskTicketLinks.*, materials.*, export.*, import.*, dialogs.*, and filters.deadline.*.
  • (2026-04-07, F001 audit) Confirmed missing MSP key groups for sub-batch B: templates.list.*, templates.create.*, templates.apply.*, templates.detail.*, templates.editor.*, templates.taskForm.*, and templates.wizard.* with nested per-step groups (basics, phases, tasks, review, clientPortal).
  • (2026-04-07, F001 audit) Confirmed missing MSP key groups for sub-batch C: settings.statuses.tenant.* / project-settings page copy, plus small dialog/filter leaf keys reused by CreateTaskFromTicketDialog, LinkTicketToTaskDialog, MoveTaskDialog, DuplicateTaskDialog, ProjectInfo, ProjectTaskStatusSelector, ProjectPhases, TaskStatusSelect, TicketSelect, and TaskTypeSelector.
  • (2026-04-07, F001 audit) Reuse decisions from current inventory: keep attachments copy under existing documents.* where strings match; add taskDocuments.* only for task-specific actions if required. Keep shared generic buttons in common. Reuse tasks.*, fields.*, phases.*, and status.* before adding narrower keys. Keep all template-related strings in features/projects under templates.*.
  • (2026-04-07, F001 audit) Representative concrete gaps seen in code: Projects.tsx needs translations for Projects, search/filter placeholders, Open menu, and delete success toast. ProjectQuickAdd.tsx / ProjectDetailsEdit.tsx need full form labels, placeholders, unsaved/save confirmation copy, and portal visibility label. TaskDependencies.tsx lacks keys for section title, dependency editor actions, and task picker placeholders. TaskTicketLinks.tsx lacks keys for duplicate/invalid ticket toasts, section title, link/create dialog labels, and ticket search filters. TaskDocumentsSimple.tsx lacks keys for auth/validation toasts, create/upload/link buttons, remove actions, document-name placeholder, and unsaved-change dialog. PhaseTaskImportDialog.tsx needs a large import.* subtree for CSV instructions, mapping labels, preview stats, unmatched agents/statuses, and completion summaries.
  • (2026-04-07, F002) Expanded server/public/locales/en/features/projects.json from 128 leaf keys to 665 leaf keys. Added new top-level groups: projectList, quickAdd, edit, projectDetail, taskForm, taskDependencies, taskTicketLinks, taskDocuments, materials, export, import, dialogs, filters, projectInfo, projectPhases, selectors, plus large settings.* and templates.* extensions.
  • (2026-04-07, F002) Chose pragmatic scaffolding over late piecemeal key creation: the English namespace now contains concrete fallbacks for all plan groups, including template list/create/apply/detail/editor/wizard surfaces and project-settings/task- status-library surfaces. Later component wiring can mostly reuse these keys and only add narrowly missing leaves if a file surfaces unexpected copy.
  • (2026-04-07, F003) Propagated the expanded features/projects tree into fr/es/de/nl/it/pl by deep-merging each existing locale over the new English source. Result: all six real locales now preserve their pre-existing translated values while gaining the full expanded key set for parity with English. Newly introduced leaves that did not previously exist are currently seeded from English; this keeps validation and wiring unblocked and preserves the prior human translations intact.
  • (2026-04-07, F004) Ran node scripts/generate-pseudo-locales.cjs after the namespace expansion. Regenerated xx/features/projects.json and yy/features/projects.json; the run also refreshed xx/common.json because the pseudo generator rewrites every pseudo-locale file from current English sources in one pass.
  • (2026-04-07, validation) node scripts/validate-translations.cjs passes after the locale propagation + pseudo generation (8 locales checked, 0 errors, 0 warnings).
  • (2026-04-07, F005) Verified template-route namespace loading with the actual resolver via node_modules/.bin/tsx -e ...getNamespacesForRoute(...). Results: /msp/projects/templates["common","msp/core","features/projects"] /msp/projects/templates/123["common","msp/core","features/projects"] /msp/projects/templates/create["common","msp/core","features/projects"] No explicit ROUTE_NAMESPACES entries are needed; longest-prefix matching against /msp/projects already loads features/projects correctly.
  • (2026-04-07, F020) Wired packages/projects/src/components/ProjectDetail.tsx to useTranslation(['features/projects', 'common']). Translated the project-detail header/view controls, search/filter chrome, sticky-status/pin controls, selected-phase completion summary, empty-state guidance, confirmation dialogs, and the main toast copy for task/phase move/update/delete/import flows.
  • (2026-04-07, F020) Added the missing projectDetail.* leaves required by the ProjectDetail.tsx wiring to English, re-synced fr/es/de/nl/it/pl via the merge script, regenerated pseudo-locales, and re-ran translation validation successfully.
  • (2026-04-07, F020 check) node_modules/.bin/eslint packages/projects/src/components/ProjectDetail.tsx passes with pre-existing warnings only (no new lint errors introduced by the i18n wiring).
  • (2026-04-07, F021) Wired packages/projects/src/components/TaskForm.tsx to useTranslation(['features/projects', 'common']). Localized the form labels, placeholders, save/delete/time-entry actions, validation copy, checklist chrome, document/ticket cleanup confirmations, dependency-unsaved prompt, and task-level toast / error fallback copy for move/save/delete/duplicate/agent flows.
  • (2026-04-07, F021) Extended taskForm.* with the missing leaves surfaced by the TaskForm audit: field labels (descriptionLabel, dueDateLabel, taskTypeLabel, priorityLabel), picker/help copy (noService, addTeamMembers, loading), confirmation/dialog strings (deleteMessage, moveMessage, cancelMessage, dependencyUnsavedMessage, keep/delete document/ticket actions), and operational fallback/toast strings (saveFailed, deleteFailed, moveFailed, duplicateFailed, linkingPartialFailure, tagCreationPartialFailure, prepareTimeEntryFailed, etc.).
  • (2026-04-07, F021) Re-synced fr/es/de/nl/it/pl by deep-merging each locale over the updated English features/projects tree, regenerated xx/yy via node scripts/generate-pseudo-locales.cjs, and re-validated translations successfully.
  • (2026-04-07, F021 check) node_modules/.bin/eslint packages/projects/src/components/TaskForm.tsx passes with pre-existing warnings only (25 warnings, 0 errors). The new i18n wiring did not add fresh lint failures.
  • (2026-04-07, F022) Wired packages/projects/src/components/PhaseTaskImportDialog.tsx to useTranslation(['features/projects', 'common']). Localized the upload step, field-mapping table, preview summary/table chrome, invalid-row / unmatched-agent / unmatched-status guidance, large-import confirmations, resolution workflows, importing spinner, and completion summaries.
  • (2026-04-07, F022) Extended import.* with the dialog-specific gaps surfaced by the audit: CSV read/process/import fallback errors, required/optional field lists, table/tooltip labels, large-import confirmation copy, unmatched agent/status warnings, next-step/import button text, row-limit description, task-count summaries, and import.fields.* labels so the field-mapping UI no longer depends on hardcoded English constants from TASK_IMPORT_FIELDS.
  • (2026-04-07, F022) Re-synced fr/es/de/nl/it/pl from the updated English source, regenerated pseudo-locales, and re-ran node scripts/generate-pseudo-locales.cjs && node scripts/validate-translations.cjs successfully.
  • (2026-04-07, F022 check) node_modules/.bin/eslint packages/projects/src/components/PhaseTaskImportDialog.tsx passes with pre-existing warnings only (6 warnings, 0 errors).
  • (2026-04-07, F023) Wired packages/projects/src/components/Projects.tsx to useTranslation(['features/projects', 'common']). Localized the page title, create actions, filter placeholders, table headers, row-value fallbacks, screen-reader menu label, reset button, and the delete-success / delete-validation fallback copy.
  • (2026-04-07, F023) Extended projectList.* with the list-specific gaps surfaced by the table audit: columns.*, statusOptions.*, row fallback values (noClient, noContact, unassigned, notAvailable, thisProject), and delete-validation / delete-failure messages used by DeleteEntityDialog.
  • (2026-04-07, F023) Re-synced fr/es/de/nl/it/pl, regenerated pseudo-locales, and re-ran node scripts/generate-pseudo-locales.cjs && node scripts/validate-translations.cjs successfully after the projectList.* additions.
  • (2026-04-07, F023 check) node_modules/.bin/eslint packages/projects/src/components/Projects.tsx passes with pre-existing warnings only (17 warnings, 0 errors).
  • (2026-04-07, F024) Wired packages/projects/src/components/TaskDocumentsSimple.tsx to useTranslation(['features/projects', 'common']). Localized the attachments section header, create/upload/link controls, empty state, remove/download/save flows, drawer titles and placeholders, file-attachment viewer copy, folder-selector prompt, and the unsaved-changes confirmation dialog shown above the task drawer.
  • (2026-04-07, F024) Extended taskDocuments.* with the missing attachment-surface leaves surfaced by the audit: attachmentsTitle, short button labels (newButton, uploadButton, linkButton), empty-state/fallback names, load/create/save/remove/ download failure messages, folder-selection copy, PDF label, and unsaved-change confirm strings.
  • (2026-04-07, F024) Re-synced fr/es/de/nl/it/pl, regenerated pseudo-locales, and re-ran node scripts/generate-pseudo-locales.cjs && node scripts/validate-translations.cjs successfully after the taskDocuments.* additions.
  • (2026-04-07, F024 check) node_modules/.bin/eslint packages/projects/src/components/TaskDocumentsSimple.tsx passes with pre-existing warnings only (10 warnings, 0 errors).
  • (2026-04-07, F025) Wired packages/projects/src/components/TaskTicketLinks.tsx to useTranslation(['features/projects', 'common']). Localized the associated-tickets section title, link/create actions, link-existing dialog chrome, filter labels and active-filter chips, select-ticket prompt, cancel/link actions, quick-create checkbox, and ticket-link toast / error fallback copy.
  • (2026-04-07, F025) Extended taskTicketLinks.* with the remaining filter/dialog leaves surfaced by the audit: category / board / priority labels, active-chip templates, selectTicketPlaceholder, error fallbacks for link/remove/new-ticket flows, and small fallbacks like clientFallback and defaultNewStatus.
  • (2026-04-07, F025) Re-synced fr/es/de/nl/it/pl, regenerated pseudo-locales, and re-ran node scripts/generate-pseudo-locales.cjs && node scripts/validate-translations.cjs successfully after the taskTicketLinks.* additions.
  • (2026-04-07, F025 check) node_modules/.bin/eslint packages/projects/src/components/TaskTicketLinks.tsx passes with pre-existing warnings only (13 warnings, 0 errors).
  • (2026-04-07, F026) Wired packages/projects/src/components/TaskDependencies.tsx to useTranslation(['features/projects', 'common']). Localized the dependency section title, dependency-type labels, add/edit placeholders, action-button titles, empty state, and inline error fallbacks for add/remove/update flows in both edit and pending modes.
  • (2026-04-07, F026) Added the only missing namespace leaf surfaced by the audit: taskDependencies.updateError, used when replacing an existing dependency target fails.
  • (2026-04-07, F026) Re-synced fr/es/de/nl/it/pl, regenerated pseudo-locales, and re-ran node scripts/generate-pseudo-locales.cjs && node scripts/validate-translations.cjs successfully after the taskDependencies.updateError addition.
  • (2026-04-07, F026 check) node_modules/.bin/eslint packages/projects/src/components/TaskDependencies.tsx passes with pre-existing warnings only (14 warnings, 0 errors).
  • (2026-04-07, F027) Wired packages/projects/src/components/ProjectMaterialsDrawer.tsx to useTranslation(['features/projects', 'common']). Localized the drawer header, add-form labels and placeholders, loading/empty states, add/remove toast copy, table headers/status badges, and the unbilled-total summary.
  • (2026-04-07, F027) Extended materials.* with the small gaps surfaced by the component audit: product-search copy, add/remove failure messages, adding / addMaterial, and unknownProduct.
  • (2026-04-07, F027) Re-synced fr/es/de/nl/it/pl, regenerated pseudo-locales, and re-ran node scripts/generate-pseudo-locales.cjs && node scripts/validate-translations.cjs successfully after the materials.* additions.
  • (2026-04-07, F027 check) node_modules/.bin/eslint packages/projects/src/components/ProjectMaterialsDrawer.tsx passes cleanly with 0 warnings / 0 errors after wrapping the translation helper in useCallback.
  • (2026-04-07, F028) Wired packages/projects/src/components/ProjectTaskExportDialog.tsx to useTranslation(['features/projects', 'common']). Localized the dialog title, phase/field selection headers, select-all toggles, selected-count summaries, export / exporting / completion copy, and the export-field checkbox labels via export.fields.*.
  • (2026-04-07, F028) Added the small missing export.done leaf so the completion CTA stays "Done" instead of drifting to a generic close label.
  • (2026-04-07, F028) Re-synced fr/es/de/nl/it/pl, regenerated pseudo-locales, and re-ran node scripts/generate-pseudo-locales.cjs && node scripts/validate-translations.cjs successfully after the export.done addition.
  • (2026-04-07, F028 check) node_modules/.bin/eslint packages/projects/src/components/ProjectTaskExportDialog.tsx passes with pre-existing warnings only (2 warnings, 0 errors).
  • (2026-04-07, F029) Wired packages/projects/src/components/ProjectQuickAdd.tsx to useTranslation(['features/projects', 'common']). Localized the quick-add dialog title, field labels/placeholders, validation banner/errors, project-status add-new affordance, client-portal section header, and create/cancel button + toast copy used both on /msp/projects and in the global quick-create dialog.
  • (2026-04-07, F029) No new locale keys were required. Existing quickAdd.*, settings.statuses.addStatus, and common:actions.* leaves fully covered the dialog, so this item was a pure component-wiring pass with English fallbacks preserved.
  • (2026-04-07, F029 check) node_modules/.bin/eslint packages/projects/src/components/ProjectQuickAdd.tsx passes with a pre-existing hooks warning only (1 warning, 0 errors).
  • (2026-04-07, F030) Wired packages/projects/src/components/ProjectDetailsEdit.tsx to useTranslation(['features/projects', 'common']). Localized the edit-form labels, placeholders, validation banner, active/inactive status chip, client-portal section header, save/cancel confirmation dialogs, and the success/failure/save-button copy.
  • (2026-04-07, F030) Added one narrow locale leaf, projectEdit.updateError, so the update failure path stays under the project-edit namespace instead of falling back to a generic common save-error message. Re-synced fr/es/de/nl/it/pl, regenerated pseudo- locales, and re-ran translation validation successfully.
  • (2026-04-07, F030 check) node_modules/.bin/eslint packages/projects/src/components/ProjectDetailsEdit.tsx passes with a pre-existing hooks warning only (1 warning, 0 errors).
  • (2026-04-07, F031) Wired packages/projects/src/components/PrefillFromTicketDialog.tsx to useTranslation(['features/projects', 'common']). Localized the dialog title, search/filter labels, active-filter chips, ticket selector label, link-checkbox copy, reset/cancel action labels, and the confirm CTA.
  • (2026-04-07, F031) Reused the existing taskTicketLinks.* filter chrome instead of adding a parallel prefill-specific subtree for shared ticket filter labels. Added only dialogs.prefillFromTicket.confirm for the domain-specific confirm button text, then re-synced fr/es/de/nl/it/pl, regenerated pseudo-locales, and re-ran translation validation successfully.
  • (2026-04-07, F031 check) node_modules/.bin/eslint packages/projects/src/components/PrefillFromTicketDialog.tsx passes with pre-existing warnings only (4 warnings, 0 errors).
  • (2026-04-07, F032) Wired packages/projects/src/components/TaskListView.tsx to useTranslation(['features/projects', 'common']). Localized the responsive table headers, hidden-columns alert, phase empty state, phase/task add buttons, badge/date/ completion chrome, expand/collapse affordances, checklist/dependency tooltip copy, and task action titles.
  • (2026-04-07, F032) Added a small list-view extension to the namespace: projectDetail.hiddenColumnsAlert, projectDetail.listViewEmptyMessage, projectDetail.seeMore, projectDetail.seeLess, projectDetail.checklistItems, projectDetail.checklistSummary, projectDetail.unknownUser, projectDetail.blocksLabel, plus taskDependencies.dependsOn, taskDependencies.unknownTask, and projectPhases.addPhase. Re-synced fr/es/de/nl/it/pl, regenerated pseudo-locales, and re-ran translation validation successfully.
  • (2026-04-07, F032 check) node_modules/.bin/eslint packages/projects/src/components/TaskListView.tsx passes with pre-existing warnings only (4 warnings, 0 errors).
  • (2026-04-07, F033) Wired packages/projects/src/components/TaskCard.tsx to useTranslation(['features/projects', 'common']). Localized the card ARIA label, quick-actions menu/sr-only copy, priority tooltip, due-date chrome, see-more toggles, additional-agent / checklist / dependency tooltips, hide-tags control, and critical-path badge.
  • (2026-04-07, F033) Added a narrow kanban/task-card extension under projectDetail.*: taskCardAria, taskActions, priorityLevel, dueLabel, noDueDate, hideTags, and criticalPath. Re-synced fr/es/de/nl/it/pl, regenerated pseudo-locales, and re-ran translation validation successfully.
  • (2026-04-07, F033 check) node_modules/.bin/eslint packages/projects/src/components/TaskCard.tsx passes with pre-existing warnings only (117 warnings, 0 errors).
  • (2026-04-07, F034) Wired packages/projects/src/components/PhaseQuickAdd.tsx to useTranslation(['features/projects', 'common']). Localized the dialog title, inline validation, phase-name/description placeholders, date labels/placeholders, save and cancel actions, and the add-phase failure fallback.
  • (2026-04-07, F034) Added the small phase-quick-add leaves under projectPhases.*: phaseNamePlaceholder, descriptionPlaceholder, adding, and addError. Re-synced fr/es/de/nl/it/pl, regenerated pseudo-locales, and re-ran translation validation successfully.
  • (2026-04-07, F034 check) node_modules/.bin/eslint packages/projects/src/components/PhaseQuickAdd.tsx passes cleanly with 0 warnings / 0 errors.
  • (2026-04-07, F050) Wired packages/projects/src/components/project-templates/TemplateEditor.tsx to useTranslation(['features/projects', 'common']), including the embedded TemplateStatusColumn and template-task-card helpers in the same file. Localized the editor shell, apply/actions menu, delete confirmations, client-portal dialog, phases sidebar, kanban header controls, empty states, status-column add buttons, and all task- card menu/tooltip chrome.
  • (2026-04-07, F050) Expanded templates.editor.* with the editor-specific gaps surfaced by the audit: failure toasts (deleteFailed, clientPortalSaveFailed, addPhaseFailed, updatePhaseFailed, deletePhaseFailed, moveTaskFailed, reorderPhaseFailed, taskSaveFailed, deleteTaskFailed, updateAssigneeFailed), delete-confirmation messages, badge/action labels, sidebar guidance, status-column summary copy, list/kanban empty states, phase-duration summaries, task-card expand/collapse labels, and fallback labels like statusFallback, unknownUser, and unknownTask. Re-synced fr/es/de/nl/it/pl, regenerated pseudo-locales, and re-ran translation validation successfully.
  • (2026-04-07, F050 check) node_modules/.bin/eslint packages/projects/src/components/project-templates/TemplateEditor.tsx passes with pre-existing warnings only (8 warnings, 0 errors).
  • (2026-04-07, F051) Wired packages/projects/src/components/project-templates/TemplateTaskForm.tsx to useTranslation(['features/projects', 'common']). Localized the create/edit title, all field labels/placeholders, validation and save-error copy, checklist/dependency section chrome, additional-agent guidance, form action buttons, and the unsaved-change confirmation dialog.
  • (2026-04-07, F051) Extended templates.taskForm.* only where the existing subtree had gaps: addAction, updateAction, saving, saveFailed, taskNameRequired, addChecklistItem, dependenciesHelp, cancelEditMessage, discardChanges, continueEditing, and additionalAgentsHelp. Re-synced fr/es/de/nl/it/pl, regenerated pseudo-locales, and re-ran translation validation successfully.
  • (2026-04-07, F051 check) node_modules/.bin/eslint packages/projects/src/components/project-templates/TemplateTaskForm.tsx passes with pre-existing warnings only (3 warnings, 0 errors).
  • (2026-04-07, F052) Wired packages/projects/src/components/project-templates/wizard-steps/TemplateTasksStep.tsx to useTranslation(['features/projects', 'common']). Localized the step title and description, phase selector, empty states, task editor labels/placeholders, service and assignment copy, checklist controls, inline validation, per-task summary text, add-task CTA, and the concluding tip callout.
  • (2026-04-07, F052) Extended templates.wizard.tasks.* only for wizard-step-specific gaps: noTasksInPhase, thisPhase, durationSummaryShort, noPriority, checklistItemsSummary, tipDescription, done, and addTaskToPhase. Re-synced fr/es/de/nl/it/pl, regenerated pseudo-locales, and re-ran translation validation successfully.
  • (2026-04-07, F052 check) node_modules/.bin/eslint packages/projects/src/components/project-templates/wizard-steps/TemplateTasksStep.tsx passes cleanly with 0 warnings / 0 errors.
  • (2026-04-08, F053) Wired packages/projects/src/components/project-templates/ApplyTemplateDialog.tsx to useTranslation(['features/projects', 'common']). Localized the dialog title, validation banner, template/project/client/status/start-date fields, customization options, assignment radio labels, add-status affordance, submit/cancel actions, and success/error toast copy for load/apply flows.
  • (2026-04-08, F053) Extended templates.apply.* only where the dialog surfaced small gaps or English drift: added create, creating, and createFailed, and aligned the existing English values for startDateLabel and assignmentOptions.* to the current UI text so the migration stays fallback-safe without changing the rendered English copy. Re-synced fr/es/de/nl/it/pl, regenerated pseudo-locales, and re-ran translation validation successfully.
  • (2026-04-08, F053 check) node_modules/.bin/eslint packages/projects/src/components/project-templates/ApplyTemplateDialog.tsx passes with a pre-existing hooks warning only (1 warning, 0 errors).
  • (2026-04-08, F054) Wired packages/projects/src/components/project-templates/ProjectTemplatesList.tsx to useTranslation(['features/projects', 'common']). Localized the page title, toolbar buttons, search/category filters, table column headers, never-used fallback, row-action menu labels, delete-confirm dialog, loading state, and the user-facing load/delete error-handler copy.
  • (2026-04-08, F054) Extended templates.list.* with the only missing list-page leaves surfaced by the audit: loadFailed and deleteFailed. Re-synced fr/es/de/nl/it/pl, regenerated pseudo-locales, and re-ran translation validation successfully.
  • (2026-04-08, F054 check) node_modules/.bin/eslint packages/projects/src/components/project-templates/ProjectTemplatesList.tsx passes with pre-existing warnings only (unused getTemplateCategories import, existing loadData hooks warning; 3 warnings, 0 errors).
  • (2026-04-08, F055) Wired packages/projects/src/components/project-templates/CreateTemplateDialog.tsx to useTranslation(['features/projects', 'common']). Localized the dialog title, source-project / template-name / description / category fields, copy-options section, create/cancel actions, success toast, and the load/create error-handler copy.
  • (2026-04-08, F055) Extended templates.create.* with the only missing dialog leaves surfaced by the audit: loadFailed and createFailed. Re-synced fr/es/de/nl/it/pl, regenerated pseudo-locales, and re-ran translation validation successfully.
  • (2026-04-08, F055 check) node_modules/.bin/eslint packages/projects/src/components/project-templates/CreateTemplateDialog.tsx passes cleanly with 0 warnings / 0 errors.
  • (2026-04-08, F056) Wired packages/projects/src/components/project-templates/TemplateTaskListView.tsx to useTranslation(['features/projects', 'common']). Localized the responsive table headers, hidden-columns alert, empty state, phase/task add CTAs, untitled-phase/task- count chrome, phase timing summaries, dependency/checklist/additional-agent tooltips, unassigned fallback, status fallback, and edit/delete task action titles.
  • (2026-04-08, F056) Reused existing tasks.*, projectPhases.*, taskDependencies.*, projectDetail.*, and templates.editor.* leaves where possible. Added only templates.editor.noPhasesFound, untitledPhase, taskCount_one, and taskCount_other for the template list-view-specific fallbacks. Re-synced fr/es/de/nl/it/pl, regenerated pseudo-locales, and re-ran translation validation successfully.
  • (2026-04-08, F056 check) node_modules/.bin/eslint packages/projects/src/components/project-templates/TemplateTaskListView.tsx passes with pre-existing warnings only (unused taskTypes, priorities, and getAssigneeName; 6 warnings, 0 errors).
  • (2026-04-08, F057) Wired packages/projects/src/components/project-templates/wizard-steps/TemplateReviewStep.tsx to useTranslation(['features/projects', 'common']). Localized the step title and intro, template-information labels, status-columns header, task summary cards, task-details-by-phase heading, checklist-item count suffix, and the final ready-to- create callout.
  • (2026-04-08, F057) Extended templates.wizard.review.* with only the review-step gaps surfaced by the audit: descriptionLabel for the summary block and readyDescription for the final creation callout. Re-synced fr/es/de/nl/it/pl, regenerated pseudo-locales, and re-ran translation validation successfully.
  • (2026-04-08, F057 check) node_modules/.bin/eslint packages/projects/src/components/project-templates/wizard-steps/TemplateReviewStep.tsx passes with pre-existing warnings only (unused Layers import and unused index callback arg; 4 warnings, 0 errors).
  • (2026-04-08, F058) Wired packages/projects/src/components/project-templates/wizard-steps/TemplatePhasesStep.tsx to useTranslation(['features/projects', 'common']). Localized the step title and intro, empty state, phase-form labels/placeholders/help copy, validation message, done/cancel actions, duration/start/task summaries, add-phase buttons, reorder hint, recalculate CTAs, and the phase-timing explainer alert.
  • (2026-04-08, F058) Extended templates.wizard.phases.* for the wizard-step gaps only: intro, addFirstPhase, phaseNameRequired, daysAfterProjectStart, tasksCount, reorder/recalculate copy, and the structured about-timing labels/help text. Re-synced fr/es/de/nl/it/pl, regenerated pseudo-locales, and re-ran translation validation successfully.
  • (2026-04-08, F058 check) node_modules/.bin/eslint packages/projects/src/components/project-templates/wizard-steps/TemplatePhasesStep.tsx passes cleanly with 0 warnings / 0 errors.
  • (2026-04-08, F059) Wired packages/projects/src/components/project-templates/CreateTemplateForm.tsx to useTranslation(['features/projects', 'common']). Localized the page title, field labels/placeholders, create/cancel actions, validation toast, success toast, and create-failure error-handler copy.
  • (2026-04-08, F059) No new locale keys were required. The existing templates.create.* subtree fully covered the standalone create page after the earlier dialog work, so this item was a pure component-wiring pass.
  • (2026-04-08, F059 check) node_modules/.bin/eslint packages/projects/src/components/project-templates/CreateTemplateForm.tsx passes cleanly with 0 warnings / 0 errors.
  • (2026-04-08, F060) Wired packages/projects/src/components/project-templates/TemplateDetail.tsx to useTranslation(['features/projects', 'common']), including the inline TaskCard helper. Localized the back/use/delete actions, delete confirmation, success/failure delete flows, template metadata labels, project-phases sidebar, phase header/timing summaries, status-column empty state, status fallback, and the phase-selection empty state in the kanban area.
  • (2026-04-08, F060) Added one narrow detail-page leaf, templates.detail.selectPhase, for the kanban empty-state prompt. Re-synced fr/es/de/nl/it/pl, regenerated pseudo-locales, and re-ran translation validation successfully.
  • (2026-04-08, F060 check) node_modules/.bin/eslint packages/projects/src/components/project-templates/TemplateDetail.tsx passes with pre-existing warnings only (unused dropdown-menu imports and unused onTemplateUpdated; 12 warnings, 0 errors).
  • (2026-04-08, F061) Wired packages/projects/src/components/project-templates/wizard-steps/TemplateClientPortalStep.tsx to useTranslation('features/projects'). Localized the step title, intro paragraph, and the explanatory info alert above ClientPortalConfigEditor.
  • (2026-04-08, F061) Extended templates.wizard.clientPortal.* with the only missing step leaves: description and aboutDescription. Re-synced fr/es/de/nl/it/pl, regenerated pseudo-locales, and re-ran translation validation successfully.
  • (2026-04-08, F061 check) node_modules/.bin/eslint packages/projects/src/components/project-templates/wizard-steps/TemplateClientPortalStep.tsx passes cleanly with 0 warnings / 0 errors.
  • (2026-04-08, F062) Wired packages/projects/src/components/project-templates/TemplateCreationWizard.tsx to useTranslation(['features/projects', 'common']). Localized the wizard dialog title, WizardProgress step labels, finish CTA, and the fallback load/validation/ create error strings surfaced by the shell.
  • (2026-04-08, F062) Extended the wizard namespace with only two shell-level leaves: templates.wizard.title and templates.wizard.errors.createFailed. Re-synced fr/es/de/nl/it/pl, regenerated pseudo-locales, and re-ran translation validation successfully.
  • (2026-04-08, F062 check) node_modules/.bin/eslint packages/projects/src/components/project-templates/TemplateCreationWizard.tsx passes with pre-existing warnings only (unused exported type imports; 10 warnings, 0 errors).
  • (2026-04-08, F063) Wired packages/projects/src/components/project-templates/wizard-steps/TemplateBasicsStep.tsx to useTranslation('features/projects'). Localized the template-name/description/ category labels, placeholders, help copy, and the "What's Next?" info alert.
  • (2026-04-08, F063) Extended templates.wizard.basics.* with the missing step leaves surfaced by the audit: nameLabel, nameHelp, descriptionLabel, descriptionHelp, categoryLabel, categoryHelp, and nextHintDescription. Re-synced fr/es/de/nl/it/pl, regenerated pseudo-locales, and re-ran translation validation successfully.
  • (2026-04-08, F063 check) node_modules/.bin/eslint packages/projects/src/components/project-templates/wizard-steps/TemplateBasicsStep.tsx passes cleanly with 0 warnings / 0 errors.
  • (2026-04-08, F064) Confirmed packages/projects/src/components/project-templates/AddTemplateDialog.tsx is a zero- string wrapper around TemplateCreationWizard. It introduces no user-visible copy and requires no i18n wiring of its own, so this item is N/A by design.
  • (2026-04-08, F080) Wired packages/projects/src/components/settings/projects/TenantProjectTaskStatusSettings.tsx to useTranslation(['features/projects', 'common']). Localized the task-status-library title/description, create/import CTAs, loading and empty states, closed badge, edit/ delete actions, create/edit dialog title, status-name/preview/color/icon chrome, closed-status checkbox help, submit/cancel actions, and the save/delete/import toast / error-handler / confirm-dialog copy.
  • (2026-04-08, F080) Extended settings.statuses.* with the tenant-library-specific gaps surfaced by the audit: task-library description, import button/error copy, dialog preview labels, color/icon field labels, closed-status help text, delete-confirmation message, and update/save helper copy. Re-synced fr/es/de/nl/it/pl, regenerated pseudo-locales, and re-ran translation validation successfully.
  • (2026-04-08, F080 check) node_modules/.bin/eslint packages/projects/src/components/settings/projects/TenantProjectTaskStatusSettings.tsx passes with pre-existing warnings only (any usages in legacy import/conflict code; 8 warnings, 0 errors).
  • (2026-04-08, F081) Wired packages/projects/src/components/settings/projects/ProjectStatusSettings.tsx to useTranslation(['features/projects', 'common']). Localized the table headers, open/ closed labels and project-status hints, actions-menu SR copy, card title/description, add/import buttons, delete-dialog fallback entity name, and the update/delete/import toast / error / validation feedback.
  • (2026-04-08, F081) Extended settings.statuses.* with the small project-status page gaps surfaced by the audit: open, order, project-status description/hints, delete-validation failure, and this_status. Re-synced fr/es/de/nl/it/pl, regenerated pseudo-locales, and re-ran translation validation successfully.
  • (2026-04-08, F081 check) node_modules/.bin/eslint packages/projects/src/components/settings/projects/ProjectStatusSettings.tsx passes with pre-existing warnings only (legacy non-null assertion and any usages; 4 warnings, 0 errors). Also fixed the new useCallback(... t ...) dependency warning introduced by the i18n wiring.
  • (2026-04-08, F082) Wired packages/projects/src/components/ProjectTaskStatusEditor.tsx to useTranslation(['features/projects', 'common']). Localized the inline label, loading state, customize controls, available-statuses prompt, add/remove/reorder feedback, closed badge, empty state, move/remove titles, and the ordering help text.
  • (2026-04-08, F082) Extended settings.statuses.* with the narrow inline-editor leaves surfaced by the audit: task/phase labels, customize/project/phase helper copy, available-statuses heading, task-status load/add/remove/reorder errors, and the arrange-order hint. Re-synced fr/es/de/nl/it/pl, regenerated pseudo-locales, and re-ran translation validation successfully.
  • (2026-04-08, F082 check) node_modules/.bin/eslint packages/projects/src/components/ProjectTaskStatusEditor.tsx passes with pre-existing warnings only (legacy non-null assertions; 2 warnings, 0 errors).
  • (2026-04-08, F083) Wired packages/projects/src/components/CreateTaskFromTicketDialog.tsx to useTranslation(['features/projects', 'common']). Localized the launcher button, dialog title, project/phase/status labels and placeholders, link-ticket checkbox, and create/cancel actions.
  • (2026-04-08, F083) Extended dialogs.createTaskFromTicket.* only for the missing launcher/field-label leaves: button, projectLabel, phaseLabel, and statusLabel. Re-synced fr/es/de/nl/it/pl, regenerated pseudo-locales, and re-ran translation validation successfully.
  • (2026-04-08, F083 check) node_modules/.bin/eslint packages/projects/src/components/CreateTaskFromTicketDialog.tsx passes with a pre- existing hooks warning only (ticket.client_id effect dependency; 1 warning, 0 errors).
  • (2026-04-08, F084) Wired packages/projects/src/components/LinkTicketToTaskDialog.tsx to useTranslation(['features/projects', 'common']). Localized the launcher button, dialog title, project/phase/task labels and placeholders, cancel/link actions, and the success/error link feedback.
  • (2026-04-08, F084) Extended dialogs.linkTicketToTask.* with the missing launcher and field/button leaves: button, projectLabel, phaseLabel, taskLabel, linking, and confirm. Re-synced fr/es/de/nl/it/pl, regenerated pseudo-locales, and re-ran translation validation successfully.
  • (2026-04-08, F084 check) node_modules/.bin/eslint packages/projects/src/components/LinkTicketToTaskDialog.tsx passes with a pre- existing hooks warning only (ticket.client_id effect dependency; 1 warning, 0 errors).
  • (2026-04-08, F085) Wired packages/projects/src/components/DeadlineFilter.tsx to useTranslation(['features/projects', 'common']). Localized the trigger placeholder / display text, filter-type options, date labels, and clear/apply actions.
  • (2026-04-08, F085) No new locale keys were required. The existing filters.deadline.* subtree fully covered the component, so this item was a pure wiring pass.
  • (2026-04-08, F085 check) node_modules/.bin/eslint packages/projects/src/components/DeadlineFilter.tsx passes cleanly with 0 warnings / 0 errors.
  • (2026-04-08, F086) Wired packages/projects/src/components/settings/ProjectSettings.tsx to useTranslation('features/projects'). Localized the page title and the four top- level tab labels for project numbering, project statuses, task statuses, and task priorities.
  • (2026-04-08, F086) Extended settings.page.* with a small tabs.* group for the settings-shell navigation labels. Re-synced fr/es/de/nl/it/pl, regenerated pseudo- locales, and re-ran translation validation successfully.
  • (2026-04-08, F086 check) node_modules/.bin/eslint packages/projects/src/components/settings/ProjectSettings.tsx passes cleanly with 0 warnings / 0 errors.
  • (2026-04-08, F087) Wired packages/projects/src/components/ProjectTaskStatusSelector.tsx to useTranslation(['features/projects', 'common']). Localized the label, customize helper copy, add-existing/create-new CTAs, available-statuses heading, closed badge, move/remove titles, empty-state hint, and ordering help text.
  • (2026-04-08, F087) Extended settings.statuses.* only for the selector-specific CTAs: add_existing and create_new. Re-synced fr/es/de/nl/it/pl, regenerated pseudo-locales, and re-ran translation validation successfully.
  • (2026-04-08, F087 check) node_modules/.bin/eslint packages/projects/src/components/ProjectTaskStatusSelector.tsx passes with a pre- existing hooks warning only (legacy initialization effect dependencies; 1 warning, 0 errors).
  • (2026-04-08, F088) Wired packages/projects/src/components/MoveTaskDialog.tsx to useTranslation(['features/projects', 'common']). Localized the dialog title, body message, tree-select placeholder, validation toasts, cancel action, and confirm/ moving button text.
  • (2026-04-08, F088) Extended dialogs.moveTask.* only for the submit-button copy: moving and confirm. Re-synced fr/es/de/nl/it/pl, regenerated pseudo-locales, and re-ran translation validation successfully.
  • (2026-04-08, F088 check) node_modules/.bin/eslint packages/projects/src/components/MoveTaskDialog.tsx passes with pre-existing warnings only (unused legacy import/state/catch param; 6 warnings, 0 errors).

Commands / Runbooks

The lang-pack loop (run after every namespace edit)

node scripts/generate-pseudo-locales.cjs && node scripts/validate-translations.cjs
  • Regenerates xx/ and yy/ from English source (NEVER hand-edit pseudo-locales)
  • Validates key parity, {{variable}} tokens, pseudo-locale fill patterns, Italian accents
  • Exit code 0 = pass. Keep green before committing.

Other useful commands

  • Count strings in a component (lower bound):
    grep -cE ">[A-Z][a-zA-Z ]{2,}[a-z]<|(label|title|placeholder)[=:][ ]*['\"][A-Z]|toast\.(error|success|warning|info)\(['\"]|throw new Error\(['\"]" <file>
    
  • List all unwired MSP project components (excluding templates):
    for f in $(find packages/projects/src/components -type f -name "*.tsx" ! -name "*.test.tsx" ! -path "*/project-templates/*"); do
      grep -qE "useTranslation" "$f" || echo "$f"
    done
    
  • List all unwired project-template components:
    for f in $(find packages/projects/src/components/project-templates -type f -name "*.tsx" ! -name "*.test.tsx"); do
      grep -qE "useTranslation" "$f" || echo "$f"
    done
    
  • Reference already-wired files (copy the pattern):
    packages/projects/src/components/project-templates/TemplateStatusManager.tsx
    packages/projects/src/components/project-templates/wizard-steps/TemplateStatusColumnsStep.tsx
    packages/projects/src/components/settings/projects/ProjectTaskStatusSettings.tsx
    
  • Verify a route's namespaces (from packages/core/src/lib/i18n/config.ts):
    grep -A 1 "'/msp/projects'" packages/core/src/lib/i18n/config.ts
    
  • Parent plan: .ai/translation/MSP_i18n_plan.md (Batches 2b-21b, 2b-21c)
  • Sibling plan: ee/docs/plans/2026-04-05-msp-i18n-tickets-migration/ (2b-21a)
  • Shared namespace file: server/public/locales/en/features/projects.json
  • Route config: packages/core/src/lib/i18n/config.ts (ROUTE_NAMESPACES)
  • Validation script: scripts/validate-translations.cjs
  • Pseudo-locale generator: scripts/generate-pseudo-locales.cjs
  • Pattern reference (already wired, array form):
    • packages/projects/src/components/project-templates/TemplateStatusManager.tsx
    • packages/projects/src/components/project-templates/wizard-steps/TemplateStatusColumnsStep.tsx
    • packages/projects/src/components/settings/projects/ProjectTaskStatusSettings.tsx
  • Precedent plan (similar wiring-only work): ee/docs/plans/2026-03-20-msp-i18n-clients-assets-onboarding/
  • MSP template pages (server-side wiring):
    • server/src/app/msp/projects/templates/page.tsx
    • server/src/app/msp/projects/templates/[templateId]/page.tsx
    • server/src/app/msp/projects/templates/create/page.tsx
  • Global quick-create integration: server/src/components/layout/QuickCreateDialog.tsx
  • Settings page integration: server/src/components/settings/SettingsPage.tsx

Open Questions

  • Does /msp/projects/templates* load features/projects via best-match to /msp/projects? Action: verify at start of sub-batch B via T108. If not, add explicit ROUTE_NAMESPACES entries for /msp/projects/templates, /msp/projects/templates/create, /msp/projects/templates/[templateId].
  • features/documents loading on /msp/projects/[id] — does TaskDocumentsSimple render translated document strings? Action: check if ROUTE_NAMESPACES['/msp/projects'] needs 'features/documents' added.
  • Task dependency cycle warnings — reuse existing features/projects keys or need new taskDependencies.cycle.*? Action: check during F026 implementation.
  • Kanban drag helpers (ARIA labels) — translate or leave English (accessibility-only text may follow different conventions)? Tentative answer: translate; aria-labels are user-facing for screen reader users and should match UI locale.
  1. Setup (F001-F005): Audit gaps, extend en/features/projects.json, translate to 6 non-English locales (IT accent audit), regenerate pseudo-locales via script, verify template route namespaces. This unblocks all component wiring.
  2. Sub-batch A PR (F020-F034): 15 largest project components. Ship first while context is fresh. Start with ProjectDetail + TaskForm as they dominate.
  3. Sub-batch B PR (F050-F064): 15 project-template components. Wizard steps + editor.
  4. Sub-batch C PR (F080-F094): 30 small/settings components. Quick cleanup pass, confirm zero-string components.
  5. Closeout (F100-F102): Final lang-pack loop, update parent plan, archive scratchpad.

Risks

  • Layout breakage from long translations. German ~30% longer than English; French task/phase labels expand button widths. Mitigation: pseudo-locale (xx/yy expand strings) test exercises kanban column widths, task card layouts, wizard nav buttons.
  • Reused components in multiple contexts. ProjectQuickAdd (global QuickCreate), ProjectSettings (settings page), LinkTicketToTaskDialog/CreateTaskFromTicketDialog (tickets + projects both). Mitigation: integration tests T110-T111.
  • Missing templates namespace loading. If /msp/projects/templates* doesn't match /msp/projects best-match, templates pages render untranslated even with components wired. Mitigation: do F005 before sub-batch B.
  • Existing tests asserting English text. Many components have .test.tsx files that may assert exact English strings. Mitigation: use t('key', 'Exact English') fallback so rendered text is identical until locale changes; update tests that break.
  • Template wizard shared state. Wizard passes step data via parent context — ensure useTranslation works in all 5 step components independently without state drift.

Final Status (2026-04-08)

All features implemented (F001F102).

  • Final en/features/projects.json key count: 1,122 leaf keys (up from 128 at start)
  • Components wired: ~63 (15 sub-batch A + 15 sub-batch B + ~33 sub-batch C including 3 discovered non-zero components)
  • Confirmed zero-string: StatusColumn, KanbanBoard, ProjectPage, KanbanZoomControl, DonutChart, TaskQuickAdd, TaskEdit, HoursProgressBar, TaskPrioritySettings (9 files)
  • Discovered non-zero (wired): TaskCommentThread (~8 strings), TaskCommentForm (~3 strings), ProjectActiveToggle (~3 strings) — originally listed as zero-string in PRD
  • Deferred: ClientPortalConfigEditor.tsx — has many user-visible strings for configuring client portal visibility of project data, but is client-portal config UI. Scope for a future pass.
  • Validation: node scripts/generate-pseudo-locales.cjs && node scripts/validate-translations.cjs passes (0 errors, 0 warnings)
  • Parent plan updated: .ai/translation/MSP_i18n_plan.md — 2b-21b/c marked DONE