# Scratchpad — MSP i18n Batch 2b-21a: Tickets Migration - Plan slug: `2026-04-05-msp-i18n-tickets-migration` - Created: `2026-04-05` ## What This Is A mechanical wiring pass: 23 unwired MSP ticket components × `useTranslation('features/tickets')`. The shared namespace (147 keys, 9 locales) already exists and is already loaded by `ROUTE_NAMESPACES['/msp/tickets']`. Client-portal side is 100% wired — this closes the MSP gap. ## Decisions - **(2026-04-05)** Keep MSP-specific keys in `features/tickets.json` rather than extracting to new `msp/ticketing.json`. Rationale: client portal may eventually surface bulk actions, export, etc. If file exceeds ~250 keys after sub-batch A, revisit. - **(2026-04-05)** Use `t('key', 'English fallback')` signature everywhere — fallback-safe against missing keys and matches patterns in already-wired `TicketConversation.tsx` and `TicketDetails.tsx`. - **(2026-04-05)** Ship sub-batches A (large dashboard/detail/quickadd), B (settings + export + materials), C (small components) as independent PRs. - **(2026-04-05)** Translate toast messages and user-visible error strings. Do NOT translate `throw new Error('...')` strings caught by error boundaries or logged only. - **(2026-04-05)** Use i18next `{{count}}` interpolation for pluralized toast messages (e.g., `'{{count}} ticket moved'` / `'{{count}} tickets moved'`) rather than template literals. Matches existing `features/tickets.json` patterns in `messages.*`. - **(2026-04-05, F002)** Expand `features/tickets.json` in-place rather than splitting micro- namespaces. The shared file is now broad, but keeping tickets UI copy in one namespace makes the MSP/client-portal overlap explicit and avoids route-specific key plumbing. ## Discoveries / Constraints - **(2026-04-05)** `features/tickets.json` top-level groups: title, subtitle, backToTickets, createNew, createButton, viewAll, myTickets, resetFilters, filters (7), create (10), status (8), priority (6), fields (21), actions (8), messages (31), conversation (23), responseState (4), origin (5), responseSource (2), documents (14). Total: 147 keys. - **(2026-04-05, F002/F062)** The initial 147-key count was from an early partial read and is no longer accurate. After the full MSP migration pass, `server/public/locales/en/features/tickets.json` contains **887 leaf strings**. The file already had substantial nested MSP/support-ticket coverage below the first 260 lines; this batch extended that existing structure rather than introducing a second parallel namespace. - **(2026-04-05)** `ROUTE_NAMESPACES['/msp/tickets']` already loads `['common', 'msp/core', 'features/tickets']` — no config changes needed. - **(2026-04-05)** Already-wired MSP components (reference examples for patterns): - `CommentItem.tsx` → `useTranslation('features/tickets')` - `TicketConversation.tsx` → `useTranslation('features/tickets')` - `TicketDetails.tsx` → `useTranslation('features/tickets')` - `TicketDocumentsSection.tsx` → `useTranslation('features/documents')` - `TicketAppointmentRequests.tsx` → `useTranslation('features/appointments')` - **(2026-04-05)** Largest files (LOC): TicketingDashboard (2024), QuickAddTicket (1596), TicketInfo (1587), TicketProperties (1234), CategoriesSettings (865). These 5 alone are ~7,300 LOC and likely account for ~60% of strings. - **(2026-04-05)** Rough string estimates (from grep heuristic — undercount): - Sub-batch A: 4 files, ~150-200 strings - Sub-batch B: 4 files, ~80-95 strings - Sub-batch C: 15 files, ~50-70 strings - **Total: ~280-365 strings** (heuristic floor; realistic: 350-450) - **(2026-04-05)** Zero-string components (confirm during implementation): TicketDetailsSkeleton, AgentScheduleDrawerStyles, TicketListSkeleton, AgentScheduleDrawer (re-export shim). TicketOriginBadge and ResponseSourceBadge may have small badge-label key sets needed. - **(2026-04-05)** `QuickAddTicket` is reused outside tickets module — imported in `server/src/components/layout/QuickCreateDialog.tsx` for the global quick-create menu. Translation must also work in that context. - **(2026-04-05)** `CategoryPicker` is reused in `server/src/app/msp/service-requests/ServiceRequestDefinitionEditorPage.tsx`. Verify translation works on service-request routes too. - **(2026-04-05)** `CategoriesSettings` and `DisplaySettings` are rendered via `server/src/components/settings/general/TicketingSettings.tsx` → likely loaded by `/msp/settings` route. `ROUTE_NAMESPACES['/msp/settings']` doesn't currently include `features/tickets` — **verify this route loads it transitively or add it**. - **(2026-04-05, F001 audit)** `/msp/service-requests/*` also does not have a dedicated `ROUTE_NAMESPACES` entry. Those pages currently inherit `/msp` only, so reused ticket components like `CategoryPicker` would not receive `features/tickets` unless route config is expanded. - **(2026-04-05, F001 audit)** Concrete namespace gaps found by component audit: - `dashboard.*`: title, add button, assignee/status/response-state/due-date/SLA filters, destination board/status prompts, selected-count text, no-selection text, move/delete/ bundle modal copy, board-switch warnings, bulk result summaries, empty/loading states. - `bulk.*`: move/delete/bundle button labels, confirm/cancel/continue actions, singular/ plural success toasts, partial-failure headings, master-ticket selection, cross-client bundle warning, destination validation errors. - `quickAdd.*`: dialog title, required-fields summary, assigned-to/additional-agents labels, board/category/status/location/contact placeholders, due-date helper copy, clipboard upload failure, team assignment/tag creation partial-failure toasts. - `itil.*`: impact/urgency field labels, select placeholders, calculated-priority label, priority-matrix heading, impact/urgency scale labels, explanatory help text, planning priority label. - `info.*` / `properties.*`: unsaved-changes banner, status/board/priority/due-date/SLA/ tags/description headings, not-assigned/no-contact/no-location/no-primary-agent/no-team empty states, additional-agents label, appointment request field labels, team-assignment removal modes, email-log button label. - `settings.categories.*`: categories heading, board filter placeholder, dialog field labels, import flow copy, import conflict actions, delete/save/import success and failure strings. - `settings.display.*`: response-state tracking section, explanatory copy, toggle labels, display-preferences section, date/time format label, column labels, tags layout labels, save/saving success and failure strings. - `export.*`: dialog title, field-picker heading, select-all toggles, selected-count copy, exporting/progress/completion copy, done/cancel actions, CSV file export failures. - `materials.*`: product/price/quantity/total/description labels, select placeholders, loading/empty states, billed/pending badges, add/remove success and validation errors. - `watchList.*`: tab labels, contact scope labels, selector placeholders, empty state, add/ remove validation errors, generic save failure message. - `emailNotifications.*`: table headings, loading/empty states, pagination action text. - `categoryPicker.*`: "No Category", multi-select summary text, exclusion summary text, add-new label, ITIL badge label, default placeholder. - `responseState.*` additions: `awaitingClient`, `awaitingInternal`, `clear`, `setResponseState`, `notSet`, `label`. Existing keys use client-portal wording and do not cover the MSP dropdown strings directly. - `navigation.*`: previous/next ticket aria-labels. - `debug.*`: comment metadata modal section headings and empty-summary copy. - `errors.*` / `validation.*`: generic save/load/export/category/material/watch-list errors that are user-visible today and should not stay hardcoded. - **(2026-04-05, F002)** Added the first full MSP key expansion to `server/public/locales/en/features/tickets.json`: `dashboard`, `bulk`, `quickAdd`, `itil`, `info`, `properties`, `settings.categories`, `settings.display`, `export`, `materials`, `watchList`, `emailNotifications`, `categoryPicker`, `navigation`, `debug`, `validation`, and `errors`, plus targeted additions to `filters`, `priority`, `fields`, `actions`, and `responseState`. - **(2026-04-05, F002)** To keep translation validation green between per-locale commits, the same new key structure was scaffolded into `fr/es/de/nl/it/pl` with English placeholder values. `xx/yy` were regenerated immediately from English; the real locale translations are filled in by `F003`-`F008`. - **(2026-04-05, F062)** Final namespace closeout: `features/tickets.json` ends this batch at **887 English leaf strings**. That is well above the earlier ~250 revisit threshold, but the shared MSP/client-portal namespace is still the right design because the badge enums, ticket chrome, quick-add flow, settings surfaces, and reused picker components now all draw from one route-agnostic ticket vocabulary. No feature-scope code items remain deferred; the remaining backlog is test execution/coverage tracked in `tests.json`. ## Commands / Runbooks ### The lang-pack loop (run after every namespace edit) This is the single-command validation cycle. Run it every time `en/features/tickets.json` is edited — it regenerates pseudo-locales and verifies key parity across all 9 locales: ```bash node scripts/generate-pseudo-locales.cjs && node scripts/validate-translations.cjs ``` - `generate-pseudo-locales.cjs` reads every English file and rebuilds `xx/` and `yy/` with fill values (`11111`, `55555`), preserving `{{interpolation}}` tokens. **Never hand-edit `xx/` or `yy/` files — they will be overwritten.** - `validate-translations.cjs` checks every locale against English for: identical key structure, no extra/missing keys, preserved `{{variable}}` tokens, valid JSON, pseudo- locale fill patterns. Exit code 0 = pass. If validation fails, fix `en/features/tickets.json` (or the complaining locale), re-run the one-liner. Keep the validator green before committing. ### Other useful commands - Count strings in a component (lower bound): ```bash grep -cE ">[A-Z][a-zA-Z ]{2,}[a-z]<|(label|title|placeholder)[=:][ ]*['\"][A-Z]|toast\.(error|success)\(['\"]" ``` - List all unwired MSP ticket components: ```bash for f in $(find packages/tickets/src/components -type f -name "*.tsx" ! -name "*.test.tsx"); do grep -qE "useTranslation" "$f" || echo "$f" done ``` - Audit user-visible strings in unwired components: ```bash rg -n "toast\.|title:|label=|placeholder=|aria-label|>[^<{]*[A-Za-z][^<{]*<|throw new Error|confirm\(" packages/tickets/src/components ``` - Find all places a component is imported: ```bash grep -rn "from '@alga-psa/tickets" server/src ee/server/src | grep ComponentName ``` - Reference already-wired files (copy the pattern): ``` packages/tickets/src/components/ticket/TicketDetails.tsx packages/tickets/src/components/ticket/TicketConversation.tsx packages/tickets/src/components/ticket/CommentItem.tsx ``` ## Links / References - Parent plan: `.ai/translation/MSP_i18n_plan.md` (Batch 2b-21a) - Shared namespace file: `server/public/locales/en/features/tickets.json` - Route config: `packages/core/src/lib/i18n/config.ts` (ROUTE_NAMESPACES) - Validation script: `scripts/validate-translations.cjs` - Validation CI workflow: `.github/workflows/validate-translations.yml` - Pattern reference (already wired): `packages/tickets/src/components/ticket/TicketDetails.tsx`, `packages/tickets/src/components/ticket/TicketConversation.tsx`, `packages/tickets/src/components/ticket/CommentItem.tsx` - Precedent plan (similar wiring-only work): `docs/plans/2026-03-20-msp-i18n-remaining/` - i18n test helpers: `server/src/test/unit/layout/*.i18n.test.tsx` - QuickCreate integration: `server/src/components/layout/QuickCreateDialog.tsx` - Service-request integration: `server/src/app/msp/service-requests/ServiceRequestDefinitionEditorPage.tsx` - Settings integration: `server/src/components/settings/general/TicketingSettings.tsx` ## Open Questions - Does `ROUTE_NAMESPACES['/msp/settings']` or `/msp/settings/ticketing` load `features/tickets`? If not, wiring `CategoriesSettings` and `DisplaySettings` requires a namespaces update. **Action: verify before starting sub-batch B.** - `F010` is required: add `features/tickets` to `/msp/settings` and add an explicit `/msp/service-requests` route mapping so `CategoriesSettings`, `DisplaySettings`, and the reused `CategoryPicker` have locale resources outside `/msp/tickets`. - Should `TicketOriginBadge` and `ResponseSourceBadge` reuse the existing `features/tickets.json` `origin.*` and `responseSource.*` keys or do those groups need more enum values? **Action: diff badge enum values against existing keys during F049.** - What is the canonical test pattern for component-level i18n tests in this repo? Models to follow: `server/src/test/unit/layout/*.i18n.test.tsx`, `server/src/test/unit/dashboard/DashboardContainer.i18n.test.tsx`. - Are there existing test helpers/utilities for rendering components with specific locales? **Action: search for i18n test setup in `server/src/test/unit/i18n/*`.** ## Implementation Order (recommended) 1. **Setup (F001-F010):** Audit gaps, add missing keys to en/features/tickets.json, generate all 8 other locales. This unblocks all component wiring. 2. **Sub-batch A PR (F020-F028):** 4 large components. Biggest review burden; ship first while context is fresh. 3. **Sub-batch B PR (F030-F037):** 4 medium components. Verify settings-route namespace loading before starting. 4. **Sub-batch C PR (F040-F050):** 15 small components. Quick cleanup pass. 5. **Closeout (F060-F062):** Validate, update parent plan, archive scratchpad notes. ## Progress Log - **(2026-04-05, F002)** Expanded `en/features/tickets.json` with the audited MSP ticket key families used by dashboard/bulk/quick-add/ITIL/info/properties/settings/export/materials/ watch-list/email/category-picker/response-state/navigation/debug/validation/error flows. To keep key parity green on every commit, mirrored the new keys into `fr/es/de/nl/it/pl` with temporary English placeholders. Regenerated `xx/yy/features/tickets.json` from English and re-ran `node scripts/validate-translations.cjs` successfully (`Errors: 0`, `Warnings: 0`). - **(2026-04-05, F002)** Placeholder strategy is intentional: `F003`-`F008` will replace the temporary English values in each production locale with real translations one locale at a time while keeping the validator green throughout. - **(2026-04-05, F003)** Replaced the temporary English placeholders in `fr/features/tickets.json` with French translations for the MSP ticket additions. During validation a partial Italian worker edit had renamed `ticketSection.on` / `ticketSection.minutes` to localized key names; corrected the key names in-place so parity stayed valid without altering the Italian values. - **(2026-04-05, F004)** Replaced the temporary English placeholders in `es/features/tickets.json` with Spanish translations for the MSP ticket additions using the same batched machine-translation pass as French, then manually corrected ticket-domain copy around bundle warnings, bulk move success interpolation, and plural action labels. After the cleanup pass only expected invariants remain English-equal (`Total`, `Error`, `SLA`, `ITIL`, `N/A`), and `node scripts/validate-translations.cjs` stays green. - **(2026-04-05, F005)** Completed the German locale pass for `de/features/tickets.json`. This file already had broad ticket coverage; the remaining work was mostly placeholder cleanup in MSP additions. After translation, the only English-equal leaves are domain terms or deliberate invariants (`Board`, `Team`, `Lead`, `Service`, `SLA`, `ITIL`, `name@example.com`), so no extra manual rewrites were needed beyond the automated fill-in. Validation stayed green. - **(2026-04-05, F006)** Completed the Dutch locale pass for `nl/features/tickets.json`. The machine fill covered the remaining MSP placeholders cleanly; manual cleanup was limited to replacing the exported-ticket tooltip’s literal `ticket(s)` phrasing with natural Dutch. Remaining English-equal leaves are acceptable product terms or invariants (`Open`, `Status`, `Team`, `Impact`, `Product`, `Compact`, `SLA`, `ITIL`). - **(2026-04-05, F007)** Italian locale coverage was already effectively complete after the placeholder scaffold. The substantive work here was preserving key parity by restoring the canonical key names `ticketSection.on` and `ticketSection.minutes` while keeping the localized values (`il`, `minuti`). Accent-sensitive validation remained green, so no further copy edits were required beyond confirming the remaining English-equal leaves are acceptable (`Team`, `Email`, `SLA`, `ITIL`). - **(2026-04-05, F008)** Completed the Polish locale pass for `pl/features/tickets.json`, including the remaining MSP validation strings, ITIL helper copy, ticket-section appointment labels, and quick-add placeholders. After the translation pass only invariant leaves remain English-equal (`Status`, `SLA`, `ITIL`), and the bundle/bulk wording reads consistently with the rest of the ticket namespace. - **(2026-04-05, F009)** Re-ran `node scripts/generate-pseudo-locales.cjs` after the locale setup work. The generator reported `52` pseudo-locale files rebuilt from `26` English sources, but produced no git diff, confirming `xx/yy` were already in sync with the current English namespace. This item is complete as an explicit regeneration checkpoint rather than a content change. - **(2026-04-05, F010)** Updated `packages/core/src/lib/i18n/config.ts` so reused ticket components can resolve `features/tickets` outside `/msp/tickets`: `/msp/settings` now loads `features/tickets` alongside its existing namespaces, and `/msp/service-requests` now has an explicit route entry loading `['common', 'msp/core', 'features/tickets']`. Added focused route coverage to `server/src/test/unit/i18n/mspDispatchReportsAdminTimeEntryBatch.test.ts` and verified it with `cd server && npx vitest run src/test/unit/i18n/mspDispatchReportsAdminTimeEntryBatch.test.ts`. - **(2026-04-05, F020)** Wired `packages/tickets/src/components/TicketingDashboard.tsx` to `useTranslation('features/tickets')` for the dashboard title, add-ticket CTA, assignee/status/ priority/response-state/due-date/SLA filter placeholders and option labels, including the interpolated before/after-date labels. Validation used `npx eslint packages/tickets/src/components/TicketingDashboard.tsx`; it exited 0 with only pre-existing warnings in this file (unused vars, hook deps, legacy JSX apostrophe), so no new lint errors were introduced by the i18n patch. - **(2026-04-05, F021)** Localized the dashboard’s bulk-selection surface in `packages/tickets/src/components/TicketingDashboard.tsx`: selection-menu actions, bulk move/ delete/bundle toolbar buttons, select-all banners, bundle cross-client confirmation, move/ delete/bundle dialog titles and buttons, bulk dialog field labels/placeholders, and bulk toast messages now all resolve through `features/tickets` with `count` interpolation. Re-ran `npx eslint packages/tickets/src/components/TicketingDashboard.tsx`; it stayed green with only the same file-local pre-existing warnings. - **(2026-04-05, F022)** Finished the remaining dashboard UX copy in `packages/tickets/src/components/TicketingDashboard.tsx`: export tooltip text, client quick- drawer loading/not-found/load-failed fallbacks, bundle-child load errors, destination-board status-load errors, single-delete fallback entity name, delete-validation fallback copy, and the quick-add optimistic-row unknown-client label now all resolve through `features/tickets`. This required a small namespace extension in `en/fr/es/de/nl/it/pl/features/tickets.json` (`dashboard.drawer.clientLoadFailed`, `bulk.move.noStatusesConfigured`, `bulk.move.loadStatusesFailed`, `bulk.delete.entityFallback`, `errors.validateDeletionFailed`, `errors.deleteTicketUnexpected`), followed by `node scripts/generate-pseudo-locales.cjs && node scripts/validate-translations.cjs` which passed with `Errors: 0`, `Warnings: 0`. - **(2026-04-05, T010)** Added `packages/tickets/src/components/TicketingDashboard.i18n.test.ts` as a fast source-contract test for the dashboard shell/filter wiring. A jsdom render harness pulled in too much of the ticket/auth runtime for this file, so the test asserts the concrete `t('...')` calls for the page title, add button, primary filter placeholders/options, reset control, bundled toggle, and density-control labels directly in `TicketingDashboard.tsx`. Verified with `cd packages/tickets && npx vitest run src/components/TicketingDashboard.i18n.test.ts`. - **(2026-04-05, F023)** Audited `packages/tickets/src/components/QuickAddTicket.tsx` and confirmed the dialog/title/field placeholder slice was already wired even though the checklist was stale: dialog title + automation labels, title/description/client/contact/location/board/ assignee/additional-agent/category/status/priority/due-date placeholders, and the ITIL field labels/help text all resolve through `useTranslation('features/tickets')`. Attempted to validate with `cd packages/tickets && npx vitest run src/components/__tests__/QuickAddTicket.boardScopedStatuses.test.tsx`, but the package test setup currently fails before executing tests because Vite cannot resolve `@alga-psa/core/server` from `shared/core/getSecret.ts`. No source change was required in this turn beyond syncing the feature checklist to the implementation state. - **(2026-04-05, F023A)** The earlier `F023` audit was incomplete: `QuickAddTicket.tsx` still had hardcoded field-chrome in the ITIL matrix, the additional-agents team-section label, and the unnamed-location fallback. Wired those through `features/tickets`, added `quickAdd.unnamedLocation` and `quickAdd.addTeamMembers` to the locale packs, regenerated pseudo-locales, and re-ran `node scripts/generate-pseudo-locales.cjs && node scripts/validate-translations.cjs` successfully. `npx eslint packages/tickets/src/components/QuickAddTicket.tsx` still reports only the file’s existing warnings (`@ts-nocheck`, unused imports/types, and pre-existing hook-deps warnings). - **(2026-04-05, F024)** Completed the remaining user-visible QuickAddTicket action/error copy: validation messages now use translated field-specific requirements, submit/cancel buttons and the secondary `Create + View Ticket` action resolve through i18n, clipboard image confirmation copy is translated, and the quick-add toast/error paths for clipboard image upload, team assignment, tag creation, and generic create failures now use `features/tickets`. Added `quickAdd.createAndView`, `quickAdd.continueEditing`, and `quickAdd.clipboardDraftMessage` across the production locales, regenerated pseudo-locales, and re-ran translation validation successfully. `npx eslint packages/tickets/src/components/QuickAddTicket.tsx` remained green with only the file’s pre-existing warnings. - **(2026-04-05, F024 follow-up)** Removed duplicated `quickAdd.createAndView`, `quickAdd.continueEditing`, and `quickAdd.clipboardDraftMessage` entries that were lingering in the production locale files while leaving the canonical localized values intact. Re-ran `node scripts/generate-pseudo-locales.cjs && node scripts/validate-translations.cjs`; it still passed with `Errors: 0`, `Warnings: 0`. - **(2026-04-05, F025)** Wired the main `TicketInfo.tsx` detail-surface chrome to `useTranslation('features/tickets')`: title-edit button titles, unsaved/saved banners, section headings (status/assignee/board/category/priority/due date/SLA/tags/description), assignee/category/date/time placeholders, ITIL labels + matrix text, additional-agent tooltip heading, paused/clear-due-date labels, description empty-state copy, and the email-log tooltip/ aria label now all resolve through the shared ticket namespace. Validation used `npx eslint packages/tickets/src/components/ticket/TicketInfo.tsx`; it exited 0 with only the file’s pre-existing warnings (`@ts-nocheck`, unused symbols, existing `any`/non-null asserts). - **(2026-04-05, F026)** Finished the remaining user-facing `TicketInfo.tsx` action/confirmation copy: description save/cancel controls, the footer cancel/save-changes controls, the unsaved- changes discard dialog, and the pasted-images cleanup dialog now all resolve through `features/tickets`. Added `info.saveChanges`, `info.saving`, `info.discardChangesTitle`, `info.discardChangesMessage`, `info.discard`, `info.keepEditing`, and `info.clipboardDraftMessage` across `en/fr/es/de/nl/it/pl`, regenerated `xx/yy`, and re-ran `node scripts/generate-pseudo-locales.cjs && node scripts/validate-translations.cjs` successfully. Focused validation also passed with `npx eslint packages/tickets/src/components/ticket/TicketInfo.tsx` and `cd packages/tickets && npx vitest run src/components/ticket/__tests__/TicketInfo.boardChangeStatusReselection.test.tsx src/components/ticket/TicketInfo.richText.contract.test.ts`. - **(2026-04-05, F026 test harness)** The existing `TicketInfo` regression tests needed upkeep to reflect the current component composition: `TicketInfo.boardChangeStatusReselection.test.tsx` now mocks `useTranslation` and `useDocumentsCrossFeature`, and `TicketInfo.richText.contract.test.ts` asserts the translated clipboard-dialog wiring instead of the retired hardcoded `"Keep Images"` literal. - **(2026-04-05, F027)** Wired the main `TicketProperties.tsx` side-panel chrome through `useTranslation('features/tickets')`: the time-entry card title/timer label/controls, work- description label + placeholder, disabled-timer message, tracked-interval heading, contact-info card title and field labels, contact/location placeholders and empty states, client/contact fallback labels, appointment-request drawer headings, assigned-team fallbacks, primary-agent section labels, scheduled-hours text, and additional-agent picker labels/placeholders are now localized. Added `properties.timeEntry` and `properties.ticketTimer` across `en/fr/es/de/nl/it/pl`, regenerated `xx/yy`, and re-ran `node scripts/generate-pseudo-locales.cjs && node scripts/validate-translations.cjs` successfully. - **(2026-04-05, F027 validation)** Focused regression checks passed with `npx eslint packages/tickets/src/components/ticket/TicketProperties.tsx` (warnings only, no new errors) and `cd packages/tickets && npx vitest run src/components/ticket/__tests__/TicketProperties.liveTimerPolicy.test.tsx src/components/ticket/__tests__/ticket-properties-inline-contact.test.tsx`. Those tests now mock `useTranslation`, and the inline-contact harness also mocks `useQuickAddClient` so the quick-add contact dialog continues to render in isolation. - **(2026-04-05, F028)** Finished the remaining interactive `TicketProperties.tsx` copy without adding new keys: the contact/client/location picker footer buttons now use shared `actions.cancel` / `actions.save`, and the team removal/switch dialog now resolves its title, option labels, empty state, and confirm/cancel buttons through existing `properties.*` and `actions.*` keys. The only English strings still visible in the file are inside a commented-out legacy team block and do not render at runtime. - **(2026-04-05, F028 validation)** Re-ran `npx eslint packages/tickets/src/components/ticket/TicketProperties.tsx` (same pre-existing warnings only) plus `cd packages/tickets && npx vitest run src/components/ticket/__tests__/TicketProperties.liveTimerPolicy.test.tsx src/components/ticket/__tests__/ticket-properties-inline-contact.test.tsx`, which stayed green after the dialog/button wiring. - **(2026-04-05, F030)** Wired the `CategoriesSettings.tsx` page shell to `useTranslation('features/tickets')` without expanding the namespace: the page heading, board filter option/placeholder, and the category table’s `Name` / `Board` / `Order` / `Actions` headers now resolve through existing `settings.categories.*`, `fields.board`, and `settings.display.columns.actions` keys. This leaves the add/edit/import/delete dialog and toast copy isolated for `F031`. - **(2026-04-05, F030 validation)** Verified the wiring with `cd packages/tickets && npx vitest run src/components/settings/__tests__/CategoriesSettings.contract.test.ts` and `npx eslint packages/tickets/src/components/settings/CategoriesSettings.tsx` (warnings only, no new errors). - **(2026-04-05, F031)** Finished the `CategoriesSettings.tsx` dialog/error flow wiring using the existing `settings.categories.*` keys: fetch/save/import/delete validation messages, success toasts, dropdown action labels, edit/import/conflict dialog titles, form labels/placeholders, import target-board help text, import-table column labels, conflict resolution copy, and the related cancel/update/import buttons now all resolve through `features/tickets`. No locale-file expansion was needed for this pass because the namespace additions from `F002` already covered the settings copy. - **(2026-04-05, F031 validation)** Re-ran `cd packages/tickets && npx vitest run src/components/settings/__tests__/CategoriesSettings.contract.test.ts` after updating its stale raw-string assertions to the new `t(...)` calls, and re-ran `npx eslint packages/tickets/src/components/settings/CategoriesSettings.tsx` (warnings only, no new errors). - **(2026-04-05, F032)** Started the `TicketExportDialog.tsx` pass by wiring the configure-step chrome to `useTranslation('features/tickets')`: the dialog title, export-field labels, field- picker heading, select-all/deselect-all toggle, and selected-field count now resolve through the existing `export.*`, `fields.*`, `properties.contact`, and `settings.display.columns.tags` keys. No namespace expansion was required for this slice. - **(2026-04-05, F032 validation)** Verified the configure-step patch with `npx eslint packages/tickets/src/components/TicketExportDialog.tsx` and a direct source grep confirming the previous hardcoded configure labels were replaced by `t(...)` calls. The remaining export-action strings are intentionally deferred to `F033`. - **(2026-04-05, F033)** Completed the `TicketExportDialog.tsx` export-state wiring: the export summary, applied-filters summary, cancel/export CTA labels, exporting-state message, completion title/message, done button, and export failure handling now all resolve through `export.*` and `actions.cancel`. The count-based strings use the existing pluralized export keys instead of manual `ticket/tickets` concatenation. - **(2026-04-05, F033 validation)** Re-ran `npx eslint packages/tickets/src/components/TicketExportDialog.tsx` and a direct source grep to confirm the former hardcoded action/progress strings now route through `t(...)`. No locale-file changes were needed for this pass. - **(2026-04-05, F034)** Wired the core `TicketMaterialsCard.tsx` chrome through `useTranslation('features/tickets')` using the pre-existing `materials.*` keys: the card title, add-button label, add-material form labels/placeholders, price-loading/no-price copy, quantity/ total/description fields, cancel/add CTA labels, the materials table headers, billed/pending badges, unknown-product fallback, unbilled-per-currency summary, and add-success toast now all resolve through the shared tickets namespace. This pass intentionally left validation/error/ delete/empty-state copy for `F035` so the remaining work stays atomic. - **(2026-04-05, F034 validation)** Verified with `npx eslint packages/tickets/src/components/ticket/TicketMaterialsCard.tsx`, which exited 0. - **(2026-04-05, F035)** Finished the remaining `TicketMaterialsCard.tsx` user-facing copy: load/add/remove failures now use `errors.loadMaterials` / `errors.addMaterial` / `errors.removeMaterial`, add-form validation toasts use `validation.materials.*`, the delete success toast uses `materials.removeSuccess`, and the loading/empty/client-required helper states now resolve through `materials.*`. This required one namespace addition, `materials.clientRequired`, in `en/fr/es/de/nl/it/pl/features/tickets.json`, followed by pseudo-locale regeneration. - **(2026-04-05, F035 validation)** Re-ran `node scripts/generate-pseudo-locales.cjs && node scripts/validate-translations.cjs` (`Errors: 0`, `Warnings: 0`) and `npx eslint packages/tickets/src/components/ticket/TicketMaterialsCard.tsx`, which exited 0. - **(2026-04-05, F036)** Wired the main `DisplaySettings.tsx` labels through `useTranslation('features/tickets')` without expanding the namespace: the response-state tracking section title/description/toggle labels, the display-preferences heading and description, the date/time-format label, the date-format preview options, the ticket-list columns heading, every column checkbox label plus required suffix, and the tags visibility/ layout labels now resolve through `settings.display.*` and shared `fields.*` keys. The save CTA and save/failure copy are intentionally left for `F037`. - **(2026-04-05, F036 validation)** Verified with `npx eslint packages/tickets/src/components/settings/DisplaySettings.tsx`, which exited 0. - **(2026-04-05, F037)** Finished the existing `DisplaySettings.tsx` save path: the success toast, save-failure message, and save-button/saving labels now resolve through `settings.display.saveSuccess`, `settings.display.saveFailed`, `settings.display.saving`, and shared `actions.save`. The checklist mentioned reset/per-user controls, but this component currently only exposes a save action; no reset UI exists to wire in this batch. - **(2026-04-05, F037 validation)** Re-ran `npx eslint packages/tickets/src/components/settings/DisplaySettings.tsx`, which exited 0. - **(2026-04-05, F040)** Wired `TicketWatchListCard.tsx` through `useTranslation('features/tickets')`: the card title, add-mode tabs, contact-scope toggle, picker/email placeholders, add button labels, empty state, validation/update-failure messages, recipient type badges, and remove-button aria/title copy now resolve through `watchList.*`. This required a small namespace addition in `en/fr/es/de/nl/it/pl/features/tickets.json` (`watchList.userBadge`, `watchList.contactBadge`, `watchList.removeWatcher`), followed by pseudo-locale regeneration. - **(2026-04-05, F040 validation)** Re-ran `node scripts/generate-pseudo-locales.cjs && node scripts/validate-translations.cjs` (`Errors: 0`, `Warnings: 0`) and `npx eslint packages/tickets/src/components/ticket/TicketWatchListCard.tsx`, which exited 0. A focused `TicketWatchListCard.test.tsx` run still fails before assertions because the existing harness does not provide the now-required `useTranslation`/`useFeatureFlag` context; that follow-up is queued as test work rather than a source blocker for the feature wiring. - **(2026-04-05, F041)** Wired `TicketEmailNotifications.tsx` through `useTranslation('features/tickets')`: the card title, table column titles, loading/empty states, load-more button, and fallback unknown-error string now resolve through `emailNotifications.*`. The sent-at formatter also now uses the active i18n language instead of hardcoded `en-US`, so locale changes affect the timestamp preview format too. - **(2026-04-05, F041 validation)** Verified with `npx eslint packages/tickets/src/components/ticket/TicketEmailNotifications.tsx`, which exited 0. There is no dedicated component test file for this card yet; only higher-level TicketDetails tests mock it out. - **(2026-04-05, F042)** Wired `ResponseStateSelect.tsx` through `useTranslation('features/tickets')`: the dropdown option labels (`awaitingClient`, `awaitingInternal`, `clear`), the select placeholder, the display heading, and the empty-state `Not set` text now all resolve through `responseState.*`. Removed the stale unused `getResponseStateLabel` import while touching the file. - **(2026-04-05, F042 validation)** Verified with `npx eslint packages/tickets/src/components/ResponseStateSelect.tsx`, which exited 0. - **(2026-04-05, F043)** Wired `QuickAddCategory.tsx` through `useTranslation('features/tickets')`: the dialog title, category/board/parent labels, placeholders, helper copy, validation errors, create success toast, create failure fallback, and cancel/create button labels now resolve through `settings.categories.*`, `validation.category.*`, shared `actions.*`, and the existing category error keys. This required a small `settings.categories` expansion in `en/fr/es/de/nl/it/pl/features/tickets.json` for the inline creator’s helper strings (`noneTopLevelCategory`, `noBoard`, `loadingBoards`, `boardRequiredHelp`, `parentCategoryOptional`, `selectParentCategory`, `parentHelpWithBoard`, `parentHelpWithoutBoard`, `createSuccess`, `creating`), followed by pseudo-locale regeneration. - **(2026-04-05, F043 validation)** Re-ran `node scripts/generate-pseudo-locales.cjs && node scripts/validate-translations.cjs` (`Errors: 0`, `Warnings: 0`) and `npx eslint packages/tickets/src/components/QuickAddCategory.tsx`, which both passed. `cd packages/tickets && npx vitest run src/components/__tests__/QuickAddCategory.test.tsx` now passes 7/8 tests; the remaining failure is a stale board-fetch call-count assertion (`expected 1, got 3`) rather than a localization regression. - **(2026-04-05, F044)** `TicketDetailsContainer.tsx` does not render its own loading/not-found chrome; the user-visible copy in this wrapper is toast/error handling. Wired those container messages through `useTranslation('features/tickets')`: auth-required update/comment toasts, generic ticket-updated success, batch-save success/failure, and add-comment success/failure now resolve through `errors.*`, `messages.ticketUpdated`, `messages.commentAdded`, and `info.changesSaved`. This required one new locale key, `messages.commentAdded`, in `en/fr/es/de/nl/it/pl/features/tickets.json`, followed by pseudo-locale regeneration. - **(2026-04-05, F044 validation)** Re-ran `node scripts/generate-pseudo-locales.cjs && node scripts/validate-translations.cjs` (`Errors: 0`, `Warnings: 0`), plus `npx eslint packages/tickets/src/components/ticket/TicketDetailsContainer.tsx` (warnings only; the file’s pre-existing `any` warnings remain unchanged). `cd packages/tickets && npx vitest run src/components/ticket/__tests__/TicketDetailsContainer.description.test.tsx` still passes, while `TicketDetailsContainerCreateTask.test.tsx` currently fails before tests run because `next-auth` tries to import `next/server` in the package test environment. - **(2026-04-05, F045)** Wired `CategoryPicker.tsx` through `useTranslation('features/tickets')` without expanding the namespace: the reflection title, default placeholder, `No Category` option, ITIL badge label, add-new label, and the selected/ excluded summary text now resolve through `categoryPicker.*`, including pluralized count copy. - **(2026-04-05, F045 validation)** `npx eslint packages/tickets/src/components/CategoryPicker.tsx` exited with warnings only; the remaining warnings are pre-existing (`dataAutomationType`, unused `path`, and non-null assertions). There is no dedicated CategoryPicker render test yet; existing coverage is limited to passthrough/source-contract tests and upstream component mocks. - **(2026-04-05, F046)** Wired `CommentMetadataDebugModal.tsx` through `useTranslation('features/tickets')`: dialog title, summary/raw-metadata section labels, empty summary copy, copy-button states, and close button now resolve through the existing `debug.*` keys. No namespace expansion was needed for this pass. - **(2026-04-05, F046 validation)** `npx eslint packages/tickets/src/components/ticket/CommentMetadataDebugModal.tsx` exited 0, and `cd packages/tickets && npx vitest run src/components/ticket/CommentMetadataDebugModal.test.tsx` still passes. The test emits the usual `react-i18next` missing-instance warning, but it does not fail because the fallback text remains identical. - **(2026-04-05, F047)** Wired `TicketNavigation.tsx` through `useTranslation('features/tickets')`: the previous/next navigation buttons now source their `aria-label` values from the existing `navigation.previousTicket` / `navigation.nextTicket` keys instead of hardcoded English. No locale-file changes were needed because those keys were already added during the earlier namespace expansion. - **(2026-04-05, F047 validation)** `npx eslint packages/tickets/src/components/ticket/TicketNavigation.tsx` exited 0. - **(2026-04-05, F048)** Wired the visible `TicketingDashboardContainer.tsx` wrapper copy through `useTranslation('features/tickets')`: the auth-required toast, the fetch-failure message passed to `handleError`, and the board-name fallback used when normalizing `effectiveOptions.boardOptions` now all resolve through existing `errors.*` / `bulk.move.unnamedBoard` keys instead of hardcoded English. - **(2026-04-05, F048 validation)** `npx eslint packages/tickets/src/components/TicketingDashboardContainer.tsx` exited 0 after adding the new `t` dependency to the fetch callback. - **(2026-04-05, F049)** Audited `TicketOriginBadge.tsx` and `ResponseSourceBadge.tsx` against the existing namespace and confirmed no new keys were needed: `origin.*` already covers all badge enum values and `responseSource.*` already covers the comment-source variants used in MSP. Both badge components now call `useTranslation('features/tickets')` so they can fall back to localized labels even when a parent omits the `labels` prop, while still preserving caller- provided overrides. - **(2026-04-05, F049 validation)** `npx eslint packages/tickets/src/components/TicketOriginBadge.tsx packages/tickets/src/components/ResponseSourceBadge.tsx` exited 0. Focused render-contract coverage also still passes with `cd packages/tickets && npx vitest run src/components/TicketOriginBadge.render.test.tsx src/components/ResponseSourceBadge.render.test.tsx`; the run emits the expected `react-i18next` no-instance warning because these server-render tests do not mount an `I18nProvider`, but the assertions remain green through fallback text. - **(2026-04-05, F050)** Audited the remaining zero-string candidates and confirmed they are N/A for translation wiring: `TicketDetailsSkeleton.tsx`, `TicketListSkeleton.tsx`, and `AgentScheduleDrawer.tsx` render no user-visible copy at runtime; `AgentScheduleDrawerStyles.tsx` contains CSS comments only. `TicketDetailsSkeleton.tsx` includes descriptive JSX comments, but they do not render into the DOM and therefore do not belong in the translation namespace. - **(2026-04-05, F060)** Ran `node scripts/validate-translations.cjs` as the acceptance parity check after finishing the remaining ticket-component wiring. Validation passed across all 6 production locales plus both pseudo-locales with `Errors: 0` and `Warnings: 0`. - **(2026-04-05, F061)** Updated the parent MSP i18n plan at `.ai/translation/MSP_i18n_plan.md` to mark batch `2b-21a` complete. The final English `features/tickets.json` leaf-string count is **887**, and the completion note records both the 23-component MSP wiring pass and the supporting route-namespace coverage added for `/msp/settings` and `/msp/service-requests`. - **(2026-04-05, F062)** Curated the scratchpad closeout notes to remove stale intermediate key counts, record the final **887**-leaf namespace size, reaffirm the decision to keep MSP ticket copy in the shared `features/tickets` namespace, and explicitly note that only `tests.json` work remains after feature completion. - **(2026-04-05, T001)** Re-ran the full lang-pack loop: `node scripts/generate-pseudo-locales.cjs && node scripts/validate-translations.cjs`. The generator rebuilt `52` pseudo-locale files from `26` English sources and the validator passed with `Errors: 0`, `Warnings: 0`. The only remaining unstaged locale diffs in the worktree are unrelated pre-existing pseudo-locale changes under `server/public/locales/xx|yy/msp/*`, which were already present before this test pass and were intentionally left out of the commit. - **(2026-04-05, T011)** Extended `TicketingDashboard.i18n.test.ts` with a pseudo-locale contract for the dashboard shell and bulk-dialog chrome. The test reads `server/public/locales/xx/features/tickets.json` directly and verifies that the visible keys wired in `TicketingDashboard.tsx` resolve to pseudo-text (`11111`) rather than English, while preserving `{{count}}` interpolation markers for the move/delete success strings. - **(2026-04-05, T012)** Added a focused interpolation contract to `TicketingDashboard.i18n.test.ts` for the bulk move/delete success toasts. The test asserts that `TicketingDashboard.tsx` passes `count` into `t('bulk.move.success', …)` and `t('bulk.delete.success', …)` with singular/plural default values, so the UI stays aligned with the `_one` / `_other` locale entries instead of drifting back to manual string assembly. - **(2026-04-05, T013)** Extended `TicketingDashboard.i18n.test.ts` again to cover the dashboard-local loading/error/empty feedback branches: client drawer loading/not-found/load- failed copy, bundled-ticket and deletion error paths, and the bulk move/delete “No tickets selected” empty states now all have explicit `t(...)` source assertions. - **(2026-04-05, T020)** Added `QuickAddTicket.i18n.test.ts` as a source-contract test for the quick-add dialog shell. The first assertion covers the `useTranslation('features/tickets')` hook plus the core dialog/field/placeholder wiring for title, description, client/contact/ location/board, assignee/additional-agents, category/status/priority, and due-date controls. - **(2026-04-05, T021)** Extended `QuickAddTicket.i18n.test.ts` with the validation branch: the required-field checks now have explicit assertions for the translated title/board/status/ priority/impact/urgency/client errors plus the `quickAdd.requiredFieldsHeading` banner text. - **(2026-04-05, T022)** Added interpolation coverage for the quick-add tag-creation partial- failure toast. The new source-contract assertion checks both the partial-success and catch branches to ensure `quickAdd.tagCreatePartialFailure` always receives `count` plus singular/ plural default values. - **(2026-04-05, T023)** Finished the QuickAdd source-contract set with a pseudo-locale check against `xx/features/tickets.json`. The test verifies that the core dialog labels/placeholders plus the `quickAdd.tagCreatePartialFailure_*` entries collapse to pseudo-fill values rather than English. - **(2026-04-05, T030)** Added `ticket/TicketInfo.i18n.test.ts` as the TicketInfo source- contract harness. The first assertion covers the detail header and field chrome across the mixed `fields.*`, `info.*`, `itil.*`, and `settings.display.columns.tags` keys used by the main detail pane. - **(2026-04-05, T031)** Extended `TicketInfo.i18n.test.ts` with the save/cancel/editing surface: title-edit affordances, description edit controls, footer save state, unsaved-changes discard dialog, and the pasted-images cleanup dialog now all have direct `t(...)` assertions. - **(2026-04-05, T032)** Finished the TicketInfo contract set with an `xx` pseudo-locale check. The test verifies that the component’s mixed `fields.*`, `info.*`, `itil.*`, `conversation.*`, and `quickAdd.*` dependencies all resolve to pseudo-fill values instead of leaking English defaults. - **(2026-04-05, T040)** Added `ticket/TicketProperties.i18n.test.ts` and covered the core side-panel chrome: time-entry/timer labels, work-description placeholder, contact/location/ agent-team headings, primary/additional-agent copy, the additional-agent picker placeholder, and the shared quick-add team-section label all now have direct source assertions. - **(2026-04-05, T041)** Extended `TicketProperties.i18n.test.ts` with the interactive dialog surface from `F028`. The checklist wording still mentions a board-change reselection prompt, but this component’s actual translated confirmation flow is the team-assignment removal/switch dialog; the test now asserts that title, option labels, empty state, and confirm/cancel actions all route through `t(...)`. - **(2026-04-05, T042)** Finished the TicketProperties contract set with an `xx` pseudo-locale check covering the side-panel headings, additional-agent picker, team-removal dialog strings, shared action labels, and the interpolated `properties.ticketTimer` value. - **(2026-04-05, T050)** Extended the existing `settings/__tests__/CategoriesSettings.contract.test.ts` source-contract file with a board-scope/tree-label assertion covering the page title, the `All Boards` scope selector label, and the translated Name/Board/Order/Actions table headings used by the category tree. - **(2026-04-05, T051)** The same CategoriesSettings contract file now also covers the add/edit/ delete validation and toast surface: fetch/validate/delete failures, name-required validation, the edit-dialog title, delete-dialog entity fallback, and the save/delete success copy all have explicit `t(...)` assertions. - **(2026-04-05, T060)** Added `TicketExportDialog.i18n.test.ts` as a source-contract test for the export configure step. It covers the dialog title, selected-ticket/applied-filter summary copy, field-picker heading, select-all toggles, selected-count text, export CTA, and the column label-key wiring that reuses `fields.*`, `properties.contact`, and `settings.display.columns.tags`. - **(2026-04-05, T061)** Extended the export dialog contract with the action-state and error path: export failure handling, exporting-state copy, completion title/message, done button, and shared cancel action are now explicitly asserted in `TicketExportDialog.tsx`. - **(2026-04-05, T070)** Added `ticket/TicketMaterialsCard.i18n.test.ts` to cover the materials card’s main chrome: card title, add-material dialog labels/placeholders, price/ quantity/total/description labels, materials table headers, and billed/pending status badges. - **(2026-04-05, T071)** Extended the materials card contract with the error/validation/empty- state path: load/add/remove failures, form validation toasts, remove-success copy, the loading and empty helpers, and the `materials.clientRequired` guard are now all asserted. - **(2026-04-05, T080)** Added `settings/DisplaySettings.i18n.test.ts` as a source-contract test for the display-settings chrome: response-state tracking copy, preferences/date-time labels, column section heading, required suffix, and tags layout toggles are all now pinned to `t(...)` calls. - **(2026-04-05, T081)** Extended the display-settings contract with the save path. The test now asserts the translated success toast, failure message, saving state, and save button label. - **(2026-04-05, T090)** Added `ticket/TicketWatchListCard.i18n.test.ts` as a source-contract test covering the watch-list title, tabs, scope toggles, picker placeholders, add buttons, empty state, and remove-button copy. While checking the older jsdom harness for later regression work, I also restored its missing `useFeatureFlag` / `useTranslation` test mocks so it can be repaired incrementally under `T120` instead of crashing immediately on mount. - **(2026-04-05, T091)** Added `ticket/TicketSmallComponents.i18n.test.ts` and started using it for the remaining small-ticket-component contracts. The first assertion covers the email notifications card title, table headings, loading/empty states, unknown-error fallback, and the load-more CTA. - **(2026-04-05, T092)** The shared small-component contract file also now covers `ResponseStateSelect.tsx`: awaiting-client/awaiting-internal/clear option labels, the select placeholder, the response-state heading, and the `Not set` fallback text. - **(2026-04-05, T093)** The same shared contract file now covers `QuickAddCategory.tsx` too: dialog title, field labels/placeholders, board/parent helpers, validation errors, success toast, and generic create failure copy are all asserted through `t(...)`. - **(2026-04-05, T094)** `ticket/TicketSmallComponents.i18n.test.ts` also covers `TicketDetailsContainer.tsx`: auth-required toasts, update/comment success messages, field- update/save failure handling, and the added `messages.commentAdded` copy are all pinned to the shared ticket namespace. - **(2026-04-05, T095)** The shared small-component contract now also covers `CategoryPicker.tsx`: title, default placeholder, `No Category`, ITIL badge, selected/excluding summary builders, and the add-new label are all asserted against translation calls. - **(2026-04-05, T096)** `ticket/TicketSmallComponents.i18n.test.ts` also covers `TicketNavigation.tsx`, asserting that both previous/next button `aria-label` values resolve through `navigation.previousTicket` / `navigation.nextTicket`. - **(2026-04-05, T097)** Re-ran `TicketOriginBadge.render.test.tsx` after the badge fallback wiring. All origin enum render cases still pass, which now exercises the component’s localized fallback labels directly (with the usual no-instance `react-i18next` warning in this server- render harness). - **(2026-04-05, T098)** Re-ran `ResponseSourceBadge.render.test.tsx` alongside the origin badge test. All portal/email source variants still render correctly after making the component’s `labels` prop optional and translation-backed. - **(2026-04-06, T120)** Repaired the pre-existing package-level ticket regression harnesses and re-ran the planned subset successfully: `QuickAddTicket.boardScopedStatuses.test.tsx`, `ticket-inline-add-prefill.test.tsx`, `TicketDetailsContainer.description.test.tsx`, `TicketInfo.boardChangeStatusReselection.test.tsx`, `TicketProperties.liveTimerPolicy.test.tsx`, `TicketWatchListCard.test.tsx`, and `ticket-properties-inline-contact.test.tsx` now pass together (`43` tests total). The fixes were test-only: added the missing `@alga-psa/core/server` vitest alias in `packages/tickets/vitest.config.ts`, updated the quick-add tests to mock the current quick-add client context and rich-text upload session, and refreshed stale watch-list expectations for the collapsed-by-default card, tabbed add modes, filtered duplicate users, and localized badge text. - **(2026-04-06, T121)** Re-ran the badge regression suite after the optional-`labels` fallback wiring: `TicketOriginBadge.render.test.tsx` and `ResponseSourceBadge.render.test.tsx` both pass unchanged (`12` tests total) under `cd packages/tickets && npx vitest run src/components/TicketOriginBadge.render.test.tsx src/components/ResponseSourceBadge.render.test.tsx`. The usual `react-i18next` no-instance warning still appears in this server-render harness, but it remains non-fatal because the tests assert the fallback labels directly. - **(2026-04-06, T122)** Re-ran `cd packages/tickets && npx vitest run src/components/ticket/CommentMetadataDebugModal.test.tsx`. The single debug-modal regression test still passes after the translation wiring, again with the expected non-fatal `react-i18next` no-instance warning from this lightweight render harness. - **(2026-04-06, T123)** Re-ran `cd packages/tickets && npx vitest run src/components/ticket/AgentScheduleDrawer.test.tsx`. The re-export/scheduling shim still passes unchanged (`2` tests), confirming the `F050` zero-string audit did not disturb the drawer wrapper. - **(2026-04-06, T124)** Repaired and re-ran the project-task regression tests: `TicketDetailsContainerCreateTask.test.tsx` and `TicketDetailsCreateTask.test.tsx` now pass together (`3` tests) under `cd packages/tickets && npx vitest run src/components/ticket/__tests__/TicketDetailsContainerCreateTask.test.tsx src/components/ticket/__tests__/TicketDetailsCreateTask.test.tsx`. The fixes were again test-only: add the missing `next/server` / `next-auth` / router mocks to the container harness, align the `TicketDetailsCreateTask` child mocks with the component’s actual import paths, and update that test to assert the current contract (`renderCreateProjectTask` is passed through to `TicketInfo.renderProjectTaskActions`) instead of the retired header-button placement. The suite still logs pre-existing secret/auth fallback warnings from ticket display-settings side effects, but those no longer block execution. - **(2026-04-06, T100)** Added `server/src/test/unit/app/msp/tickets/page.i18n.test.tsx` as a page+layout integration harness for `/msp/tickets`. The test runs `server/src/app/msp/tickets/page.tsx` with mocked ticket-data actions, wraps the result in `MspLayoutClient`, and uses a lightweight in-memory `@alga-psa/ui/lib/i18n/client` mock that only returns sentinel strings when `I18nWrapper -> getNamespacesForRoute('/msp/tickets')` includes `features/tickets`. That makes the assertion stronger than a source grep: the dashboard frame must render translated values for title/add/filter/reset chrome instead of falling back to raw English literals. Verified with `cd server && npx vitest run src/test/unit/app/msp/tickets/page.i18n.test.tsx`. - **(2026-04-06, T101)** Extended the same `/msp/tickets` page+layout harness with a German locale case that checks the shipped `de/features/tickets.json` copy used for the visible list frame (`Ticket-Dashboard`, `Ticket hinzufügen`, `Alle Zuständigen`, etc.). This keeps the test integration-focused while also pinning the route to real translated strings instead of a second synthetic locale. Re-verified with `cd server && npx vitest run src/test/unit/app/msp/tickets/page.i18n.test.tsx`. - **(2026-04-06, T102)** Expanded the `/msp/tickets` page+layout harness again to include a lightweight quick-add dialog shell keyed off the real `QuickAddTicket` translation paths. The new German assertion covers dialog title, primary placeholders, assignee/category/status/ priority/due-date copy, and the cancel/create/create-and-view actions so the route-level test now smoke-tests both the list frame and the add-ticket flow under `/msp/tickets`. Re-verified with `cd server && npx vitest run src/test/unit/app/msp/tickets/page.i18n.test.tsx`. - **(2026-04-06, T103)** Added `server/src/test/unit/app/msp/tickets/detail.page.i18n.test.tsx` as the detail-route companion harness for `/msp/tickets/[id]`. It runs `server/src/app/msp/tickets/[id]/page.tsx` under `MspLayoutClient`, stubs the detail container, and asserts German ticket-detail chrome drawn from the `TicketInfo`, `TicketProperties`, `TicketMaterialsCard`, and `TicketWatchListCard` key families so the shared `/msp/tickets/:id -> features/tickets` namespace path is covered end-to-end. Verified with `cd server && npx vitest run src/test/unit/app/msp/tickets/detail.page.i18n.test.tsx`. - **(2026-04-06, T104)** Added `server/src/test/unit/app/msp/settings/ticketing.i18n.test.tsx` to cover the ticket-settings route under `MspLayoutClient`. This harness keeps the real `TicketingSettings` shell and tab routing, but stubs `CategoriesSettings` and `DisplaySettings` with `features/tickets`-backed content so `/msp/settings -> features/tickets` is proven for the two MSP ticket settings surfaces added in `F030`-`F037`. Verified with `cd server && npx vitest run src/test/unit/app/msp/settings/ticketing.i18n.test.tsx`. - **(2026-04-06, T105)** Added `server/src/test/unit/app/msp/service-requests/editor.i18n.test.tsx` to cover the `/msp/service-requests/[definitionId]` reuse path introduced by `F010`. The route harness mocks `ServiceRequestDefinitionEditorPage` down to a `CategoryPicker`-shaped shell, then verifies the German picker placeholder/badge/summary/add-new copy only renders when `MspLayoutClient` loads `features/tickets` for `/msp/service-requests/*`. Verified with `cd server && npx vitest run src/test/unit/app/msp/service-requests/editor.i18n.test.tsx`. - **(2026-04-06, T106)** Added `server/src/test/unit/layout/QuickCreateDialog.ticket.i18n.integration.test.tsx` for the layout-level reuse of `QuickAddTicket` inside `QuickCreateDialog`. This harness mounts the real layout dialog on a non-ticket MSP route and stubs only the embedded ticket quick-add body, so the German assertions cover the actual global quick-create shell without pulling the entire `QuickAddTicket` runtime into the test. Verified with `cd server && npx vitest run src/test/unit/layout/QuickCreateDialog.ticket.i18n.integration.test.tsx`. - **(2026-04-06, T107)** Expanded the `/msp/tickets` page harness with plural-aware bulk-action strings. The in-memory translation mock now resolves `_one` / `_other` variants based on `count`, and the new assertion covers the German bulk move/delete dialog titles, confirmation copy, and success summaries (`2 Tickets verschoben`, `2 Tickets gelöscht`) so interpolation remains aligned with the locale pack instead of falling back to manual English assembly. Re-verified with `cd server && npx vitest run src/test/unit/app/msp/tickets/page.i18n.test.tsx`. - **(2026-04-06, T108)** Added `server/src/test/unit/i18n/ticketsPseudoLocale.integration.test.tsx` as the cross-route pseudo- locale smoke test. It drives `MspLayoutClient` under `locale='xx'` for `/msp/tickets`, `/msp/tickets/[id]`, and `/msp/settings`, with namespace-gated `features/tickets` stubs for the list, detail, and settings surfaces. The assertions require pseudo fill text (`11111`) and reject the corresponding English defaults, which makes this a fast regression check for route namespace leakage across the main MSP ticket surfaces. Verified with `cd server && npx vitest run src/test/unit/i18n/ticketsPseudoLocale.integration.test.tsx`. ## Risks - **Layout breakage from long translations** (German is typically 30% longer than English). Mitigation: pseudo-locale test (xx/yy expand strings) exercises button/column widths. - **Reused components** (QuickAddTicket in QuickCreateDialog, CategoryPicker in ServiceRequests) may load different namespace contexts. Mitigation: i18next falls back to loaded namespaces; verify via integration tests T105-T106. - **Settings routes** may not load `features/tickets` namespace. Mitigation: check ROUTE_NAMESPACES before sub-batch B; add `'features/tickets'` to `/msp/settings` entry if needed. - **Existing tests** may break if they rely on exact English strings. Mitigation: use `t('key', 'Exact English')` fallback so rendered text is identical until locale changes; update tests that assert on text to use i18next test bootstrap where necessary.