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

35 KiB

Scratchpad -- MSP i18n: Contract Lines Sub-batch

  • Plan slug: 2026-04-09-msp-i18n-contract-lines
  • Created: 2026-04-09

Decisions

  • (2026-04-09) Namespace name: msp/contract-lines -- scoped to the contract-line management UI specifically, not the broader billing section. This keeps the file manageable at ~350-400 keys.
  • (2026-04-09) Billing constants NOT translated here: BILLING_FREQUENCY_OPTIONS, PLAN_TYPE_OPTIONS, BILLING_FREQUENCY_DISPLAY, PLAN_TYPE_DISPLAY, CONTRACT_LINE_TYPE_DISPLAY, and BILLING_TIMING_OPTIONS are defined in @alga-psa/billing/constants/billing. They supply option labels consumed by multiple areas (contracts, invoices, billing dashboard). Translating them belongs in a shared msp/billing namespace or the constants themselves need to return translation keys. For this batch, the components will translate the surrounding labels but leave the constant-driven option values as-is.
  • (2026-04-09) BILLING_TIMING_OPTIONS: The two options ("Arrears -- invoice after the period closes", "Advance -- invoice at the start of the period") are defined locally in both ContractLineDialog.tsx and FixedContractLineConfiguration.tsx. These SHOULD be translated inline in this batch since they are not shared constants.
  • (2026-04-09) User type options: The userTypeOptions array (Technician, Engineer, Consultant, Project Manager, Administrator) is defined locally in ServiceHourlyConfigForm.tsx and both hourly configuration files. These should be translated in this namespace.
  • (2026-04-09) Cadence owner options: CADENCE_OWNER_OPTIONS in FixedContractLineConfiguration.tsx are local and should be translated. They include both label and description fields.
  • (2026-04-09) Currency symbol ($): The $ prefix shown in rate input fields is rendered as a static visual indicator (not a formatted currency value). It should remain hardcoded since it represents the input prefix, not a translated string. Actual currency formatting should use useFormatters() where full monetary values are displayed.
  • (2026-04-09) Duplicate strings across files: Many contract-line and preset configuration files share identical strings (e.g., "Contract Line Basics", "Reset", "Save Changes", "Saving...", "Please correct the following:"). Use shared keys under common.* in the namespace to avoid duplication.

Discoveries / Constraints

Component Structure

  • (2026-04-09) The 21 files split into two parallel hierarchies: contract-line configurations (operating on actual contract lines) and preset configurations (operating on reusable templates). The UI text differs slightly ("Contract Line" vs "Contract Line Preset"), requiring separate but similar keys.
  • (2026-04-09) ContractLineTypeRouter.tsx and ContractLinePresetTypeRouter.tsx are thin routing components with only 3-4 translatable strings each (loading text, error messages). They could potentially share a common router.* key group.
  • (2026-04-09) ContractLineDialog.tsx is the largest file (1292 lines) and contains render functions for Fixed/Hourly/Usage configs, each with its own info alerts, service lists, labels, and empty states. This needs careful key organization to avoid a flat wall of keys.
  • (2026-04-09) ContractLines.tsx is the legacy top-level view that manages both contract lines AND their services in a two-panel layout. It has its own set of column headers and action menus distinct from ContractLinesOverview.tsx.
  • (2026-04-09) GenericContractLineServicesList.tsx uses BILLING_METHOD_OPTIONS defined locally as a const -- these labels ("Fixed Price", "Hourly", "Usage Based") should be translated.

Interpolation Patterns

  • (2026-04-09) Service index in validation: Service ${index + 1}: Please select a service -- needs {{index}} interpolation.
  • (2026-04-09) Card title pattern: Edit Contract Line: ${plan?.contract_line_name || '...'} (Hourly) -- needs {{name}} interpolation and the type suffix.
  • (2026-04-09) Tier overlap messages: Tier ${i + 1} overlaps with Tier ${i + 2} -- needs {{tier1}} and {{tier2}} interpolation.
  • (2026-04-09) Rate summary: ${rate} / ${unit} -- needs {{rate}} and {{unit}} interpolation.
  • (2026-04-09) Services heading: Services for ${contractLines.find(...)?.contract_line_name} -- needs {{name}} interpolation.
  • (2026-04-09) Delete toast: Contract line deleted successfully -- simple string, no interpolation.
  • (2026-04-09) "Add Selected (N) Services" button has count interpolation: needs {{count}}.
  • (2026-04-09) Non-hourly service message: This service (Billing Method: ${billing_method}) cannot be configured... -- needs {{method}} interpolation.

Shared Components (Out of Scope)

  • (2026-04-09) ServiceCatalogPicker -- used in ContractLineDialog for selecting services. Has its own translatable strings but belongs to a shared component namespace.
  • (2026-04-09) BucketOverlayFields -- used for bucket configuration in hourly/usage presets. Shared with contracts.
  • (2026-04-09) ServiceConfigurationPanel -- used by ContractLineServiceForm to render the full config UI. Separate sub-batch.
  • (2026-04-09) DeleteEntityDialog -- shared UI component with its own translation needs.
  • (2026-04-09) ConfirmationDialog -- shared UI component used by preset service lists for unsaved changes warning.
  • (2026-04-09) DataTable -- column definitions pass translated strings, but the DataTable component itself is shared.

Potential Gotchas

  • (2026-04-09) ServiceBucketConfigForm.tsx does dynamic pluralization (pluralizeUnit()) by appending 's' to unit names. This naive pluralization will NOT work for non-English languages. The translation keys should use ICU plural syntax or separate singular/plural keys. For this batch, keep the pluralization in the key name pattern and let translators handle it.
  • (2026-04-09) Several files use toast.success() and handleError() with hardcoded English messages. These need translation but the toast calls happen in async handlers, not in JSX. The t() function from useTranslation is available in the component scope and can be used in callbacks.
  • (2026-04-09) FixedContractLineServicesList and FixedContractLinePresetServicesList are imported from the parent billing-dashboard/ directory (not the contract-lines/ subdirectory). They are OUT OF SCOPE for this batch per the file list.
  1. Create msp/contract-lines.json English namespace file (F036)
  2. Start with smaller/simpler files: routers (F034-F035), type selector (F033), edit quantity dialog (F032)
  3. Form sub-components: ServiceTierEditor (F029), ServiceUsageConfigForm (F030), ServiceBucketConfigForm (F031), ServiceHourlyConfigForm (F024-F025)
  4. Service list components: GenericContractLineServicesList (F016-F017), preset service lists (F012-F015)
  5. Configuration components: Fixed (F020-F023), Usage (F006-F009), Hourly (F004-F005, F010-F011)
  6. Top-level components: ContractLineServiceForm (F026), ContractLines (F018-F019), ContractLinesOverview (F027-F028)
  7. ContractLineDialog (F001-F003) -- largest file, do last
  8. Generate translations (F037), pseudo-locales (F038), validate (F039), update routes (F040)

Commands / Runbooks

  • Validate translations: node scripts/validate-translations.cjs
  • Generate pseudo-locales: npx ts-node scripts/generate-pseudo-locale.ts --locale xx --fill "1111"
  • Count keys: node -e "const o=JSON.parse(require('fs').readFileSync('server/public/locales/en/msp/contract-lines.json'));const c=(o,p='')=>{let n=0;for(const[k,v]of Object.entries(o)){if(typeof v==='object'&&v!==null)n+=c(v,p+k+'.');else n++}return n};console.log(c(o))"
  • Visual QA: Enable msp-i18n-enabled flag locally, switch to xx locale, navigate to Billing > Contract Lines and exercise all configuration screens

Progress Log

  • (2026-04-14) F001 completed in ContractLineDialog.tsx by wiring useTranslation('msp/contract-lines') and translating dialog title, basics labels/placeholders, and validation prefix using t(..., { defaultValue }).
  • (2026-04-14) Preemptively translated additional ContractLineDialog.tsx strings for F002/F003 in the same pass to minimize repeated churn in a 1k+ LOC file; feature flags will still be advanced one-by-one in checklist order.
  • (2026-04-14) Validation errors in validateForm() now use interpolation-based i18n keys (for example Service {{index}}...) to align with acceptance criteria for dynamic validation text.
  • (2026-04-14) Verification command run: npx eslint packages/billing/src/components/billing-dashboard/ContractLineDialog.tsx (warnings only, no errors).
  • (2026-04-14) F002 covered by the same ContractLineDialog.tsx translation pass: billing model chooser heading/description, fixed/hourly/usage card title+description strings, billing timing option labels, and fixed vs non-fixed billing timing helper text are now t() backed.
  • (2026-04-14) F003 covered in ContractLineDialog.tsx: fixed/hourly/usage config headings, alerts, service labels/placeholders, rate/unit/proration copy, bucket recommendation labels, empty states, indexed service labels, and footer action labels (Cancel, submit variants, saving state) are now translated with defaults.
  • (2026-04-14) F004 implemented in contract-lines/HourlyContractLineConfiguration.tsx: added useTranslation('msp/contract-lines'); translated plan basics card title with {{name}}, basics heading/description/labels/placeholders, plan-wide hourly settings accordion trigger text, overtime labels + tooltip + helper copy, and after-hours labels + tooltip + helper copy.
  • (2026-04-14) Validation strings for plan-wide hourly settings (overtimeRate, overtimeThreshold, afterHoursMultiplier) now use i18n keys; interpolation paths are ready for locale coverage.
  • (2026-04-14) Verification command run: npx eslint packages/billing/src/components/billing-dashboard/contract-lines/HourlyContractLineConfiguration.tsx (warnings only, no errors).
  • (2026-04-14) F005 covered in contract-lines/HourlyContractLineConfiguration.tsx: translated service rates card title, fallback service name (Service ID: {{id}}), non-hourly configurability message with {{method}}, reset/save labels, sticky save button/loading text, manage services card title, empty states, and save error fallbacks.
  • (2026-04-14) F006 implemented in contract-lines/UsageContractLineConfiguration.tsx: wired useTranslation('msp/contract-lines'), translated usage plan basics card title with {{name}} (Usage), basics section heading/description/labels/placeholders, and reset/save button labels including saving state.
  • (2026-04-14) Plan basics validation/save fallback strings in the same component now use translated defaults.
  • (2026-04-14) Verification command run: npx eslint packages/billing/src/components/billing-dashboard/contract-lines/UsageContractLineConfiguration.tsx (warnings only, no errors).
  • (2026-04-14) F007 covered in contract-lines/UsageContractLineConfiguration.tsx: translated service pricing card title, accordion summaries (Tiered Pricing ({{count}} tiers), Not Set, Loading..., {{rate}} / {{unit}}, Loading configuration...), save-all CTA/loading text, manage-services card title, empty-state helper copy, and all save/validation error strings.
  • (2026-04-14) F008 implemented in contract-lines/UsageContractLinePresetConfiguration.tsx: wired useTranslation('msp/contract-lines'), translated preset basics card title with {{name}} (Usage), Contract Line Preset Basics heading, preset-name label/placeholder, description copy, billing-frequency label/placeholder, and reset/save button labels with saving state.
  • (2026-04-14) Verification command run: npx eslint packages/billing/src/components/billing-dashboard/contract-lines/UsageContractLinePresetConfiguration.tsx (warnings only, no errors).
  • (2026-04-14) F009 covered in contract-lines/UsageContractLinePresetConfiguration.tsx: translated service-pricing card title, accordion summaries (Tiered Pricing, Not Set, Loading..., rate/unit summary), loading configuration message, save-all CTA, manage preset services card title, and save/validation errors including No changes detected to save and Cannot save, validation errors exist in the modified services.
  • (2026-04-14) F010 implemented in contract-lines/HourlyContractLinePresetConfiguration.tsx: wired useTranslation('msp/contract-lines'), translated preset basics card title with {{name}} (Hourly), basics section labels/placeholders/descriptions, minimum billable + round-up labels/help copy, and reset/save actions (including saving text).
  • (2026-04-14) Verification command run: npx eslint packages/billing/src/components/billing-dashboard/contract-lines/HourlyContractLinePresetConfiguration.tsx (warnings only, no errors).
  • (2026-04-14) F011 covered in contract-lines/HourlyContractLinePresetConfiguration.tsx: translated manage preset services card title plus all required error states (Invalid plan type or plan not found, Contract line not found or invalid type, Failed to load plan configuration) and shared save/validation error messages.
  • (2026-04-14) F012 implemented in contract-lines/UsageContractLinePresetServicesList.tsx: wired useTranslation('msp/contract-lines'); translated usage-preset services list headings/empty-state/loading text, metadata lines, action-menu screen-reader + remove action text, rate/unit labels, bucket switch label, and add-services section heading/copy.
  • (2026-04-14) Billing method display labels in local BILLING_METHOD_OPTIONS now resolve through translation keys (Fixed Price, Hourly, Usage Based).
  • (2026-04-14) Verification command run: npx eslint packages/billing/src/components/billing-dashboard/contract-lines/UsageContractLinePresetServicesList.tsx (warnings only, no errors).
  • (2026-04-14) F013 covered in contract-lines/UsageContractLinePresetServicesList.tsx: translated add-service metadata/interpolation strings, Add Selected ({{count}}) Services CTA, bucket overlay defaults/labels, save/reset controls, unsaved-change banner + confirmation dialog labels/messages, success toast, and error fallbacks.
  • (2026-04-14) F014 implemented in contract-lines/HourlyContractLinePresetServicesList.tsx: wired useTranslation('msp/contract-lines'); translated hourly-preset services list heading/empty/loading copy, service metadata line, action-menu screen-reader + remove label, hourly rate label, bucket switch label, and add-services section heading/empty-state text.
  • (2026-04-14) Local billing method labels (Fixed Price, Hourly, Usage Based) now flow through i18n keys in this component as well.
  • (2026-04-14) Verification command run: npx eslint packages/billing/src/components/billing-dashboard/contract-lines/HourlyContractLinePresetServicesList.tsx (warnings only, no errors).
  • (2026-04-14) F015 covered in contract-lines/HourlyContractLinePresetServicesList.tsx: translated add-service metadata with interpolation, Add Selected ({{count}}) Services CTA, save/reset controls, unsaved-change banner + confirmation dialog text, success toast, and save error fallbacks.
  • (2026-04-14) F016 implemented in contract-lines/GenericContractLineServicesList.tsx: wired useTranslation('msp/contract-lines'); translated all primary table columns (service name/type, billing method, derived config type, quantity, unit, custom rate, actions), billing-method display labels, config badge text (Billing mismatch, Default), and action-menu screen-reader text.
  • (2026-04-14) Verification command run: npx eslint packages/billing/src/components/billing-dashboard/contract-lines/GenericContractLineServicesList.tsx (warnings only, no errors).
  • (2026-04-14) F017 covered in contract-lines/GenericContractLineServicesList.tsx: translated add-services section heading, empty states (No services currently associated..., All available services..., Loading services...), service-detail interpolation text, custom-rate placeholder (Enter rate), Add Selected ({{count}}) Services, currency-mismatch copy (No {{currency}} price), and add/remove error messages.
  • (2026-04-14) F018 implemented in ContractLines.tsx: added useTranslation('msp/contract-lines'); translated contract-line table column headers (Contract Line Name, Billing Frequency, Contract Line Type, Is Custom, Actions), Yes/No display values, row action labels (Edit, Delete), menu screen-reader text, page heading, and Add Contract Line button text.
  • (2026-04-14) Verification command run: npx eslint packages/billing/src/components/billing-dashboard/ContractLines.tsx (warnings only, no errors).
  • (2026-04-14) F019 covered in ContractLines.tsx: translated plan-services heading, Services for {{name}} subheading interpolation, empty state (Select a contract line to manage its services), plan-services table headers, service remove action label, service selector placeholder, Add Service button, and related add/update/remove/fetch error strings plus delete-success toast.
  • (2026-04-14) F020 implemented in contract-lines/FixedContractLineConfiguration.tsx: wired useTranslation('msp/contract-lines'); translated card title with {{name}}, contract-line basics section heading/description/labels/placeholders, billing-timing option labels + helper text, and cadence-owner section labels/descriptions with translated radio options.
  • (2026-04-14) Verification command run: npx eslint packages/billing/src/components/billing-dashboard/contract-lines/FixedContractLineConfiguration.tsx (warnings only, no errors).
  • (2026-04-14) F021 covered in contract-lines/FixedContractLineConfiguration.tsx: translated fixed-fee settings heading/description, base-rate label/help, partial-period label/description, billing-cycle-alignment label/options/placeholder, associated-services card title, reset/save actions, and fixed-form validation/error messages.
  • (2026-04-14) F022 implemented in contract-lines/FixedContractLinePresetConfiguration.tsx: wired useTranslation('msp/contract-lines'); translated preset card title with {{name}} (Fixed), Contract Line Preset Basics heading/description, name and billing-frequency labels/placeholders, plus billing-timing label/helper text in the preset settings area.
  • (2026-04-14) Verification command run: npx eslint packages/billing/src/components/billing-dashboard/contract-lines/FixedContractLinePresetConfiguration.tsx (warnings only, no errors).
  • (2026-04-14) F023 covered in contract-lines/FixedContractLinePresetConfiguration.tsx: translated fixed-fee settings heading/description, Recurring Base Rate (Optional) and override helper text, proration label/description, added + translated billing-cycle-alignment selector with options (Start of Billing Cycle, End of Billing Cycle, Proportional Coverage), associated-services card title, reset/save actions, and preset validation/error messages.
  • (2026-04-14) F024 implemented in contract-lines/ServiceHourlyConfigForm.tsx: wired useTranslation('msp/contract-lines'); translated hourly-rate/minimum-billable/round-up labels, tooltip content, minute placeholders, and money placeholders while preserving existing validation rendering paths.
  • (2026-04-14) Verification command run: npx eslint packages/billing/src/components/billing-dashboard/contract-lines/ServiceHourlyConfigForm.tsx (warnings only, no errors).
  • (2026-04-14) F025 covered in contract-lines/ServiceHourlyConfigForm.tsx: translated user-type-specific-rates heading/tooltip, existing-rate display suffix, Add New Rate label, user-type select placeholder, Add button, user-type option labels (Technician, Engineer, Consultant, Project Manager, Administrator), and validation messages for missing/duplicate user type rate setup.
  • (2026-04-14) F026 implemented in contract-lines/ContractLineServiceForm.tsx: wired useTranslation('msp/contract-lines') and translated dialog title, loading state, and required error messages (Missing plan or service information, Failed to load service configuration, Failed to update service).
  • (2026-04-14) Verification command run: npx eslint packages/billing/src/components/billing-dashboard/contract-lines/ContractLineServiceForm.tsx (warnings only, no errors).
  • (2026-04-14) F027 implemented in contract-lines/ContractLinesOverview.tsx: wired useTranslation('msp/contract-lines'); translated page heading, Add Contract Line Preset button, presets table column headers, action menu labels (Edit, Delete), and screen-reader Open menu text.
  • (2026-04-14) Verification command run: npx eslint packages/billing/src/components/billing-dashboard/contract-lines/ContractLinesOverview.tsx (warnings only, no errors).
  • (2026-04-14) F028 covered in contract-lines/ContractLinesOverview.tsx: translated filter/search placeholders, All types option, reset-filters button text, loading indicator text, delete-success toast, and delete/fetch error messages.
  • (2026-04-13) F029 complete (ServiceTierEditor.tsx): added useTranslation('msp/contract-lines') and replaced all user-visible strings with t() defaults, including card title, Add Tier button, translated empty state, column headers with {{unit}} interpolation, helper text, Unlimited placeholder, and tier-indexed aria labels (Tier {{tier}} ..., Remove Tier {{tier}}).
  • (2026-04-13) F029 verification: npx eslint packages/billing/src/components/billing-dashboard/contract-lines/ServiceTierEditor.tsx (pass, no errors).
  • (2026-04-13) F030 complete (ServiceUsageConfigForm.tsx): wired useTranslation('msp/contract-lines') and translated labels/tooltips/placeholders for default rate/unit/minimum usage, required field indicator, and tiered pricing switch with {{serviceName}} interpolation.
  • (2026-04-13) F030 verification: npx eslint packages/billing/src/components/billing-dashboard/contract-lines/ServiceUsageConfigForm.tsx (pass; pre-existing warnings only: any type and unused handler).
  • (2026-04-13) F031 complete (ServiceBucketConfigForm.tsx): wired useTranslation('msp/contract-lines') and translated bucket labels/tooltips/placeholders with unit interpolation ({{unit}}/{{units}}), while preserving dynamic unit pluralization via pluralizeUnit() for runtime unit labels.
  • (2026-04-13) F031 verification: npx eslint packages/billing/src/components/billing-dashboard/contract-lines/ServiceBucketConfigForm.tsx (pass; pre-existing any warning only).
  • (2026-04-13) F032 complete (EditContractLineServiceQuantityDialog.tsx): wired useTranslation('msp/contract-lines'); translated dialog title, quantity heading, quantity/unit-price labels, helper text, validation error (Quantity must be greater than zero), fallback update error, and Cancel/Save actions.
  • (2026-04-13) F032 verification: npx eslint packages/billing/src/components/billing-dashboard/contract-lines/EditContractLineServiceQuantityDialog.tsx (pass; pre-existing any warning only).
  • (2026-04-13) F033 complete (ContractLineTypeSelector.tsx): wired useTranslation('msp/contract-lines'); translated Contract Line Type label, dropdown placeholder, and fixed/hourly/usage description copy used in cards and dropdown option descriptions.
  • (2026-04-13) F033 verification: npx eslint packages/billing/src/components/billing-dashboard/contract-lines/ContractLineTypeSelector.tsx (pass, no errors).
  • (2026-04-13) F034 complete (ContractLineTypeRouter.tsx): wired useTranslation('msp/contract-lines'); translated loading text, not-found message with {{id}}, load-failed error, and unsupported-type error with {{type}} interpolation.
  • (2026-04-13) F034 verification: npx eslint packages/billing/src/components/billing-dashboard/contract-lines/ContractLineTypeRouter.tsx (pass, no errors).
  • (2026-04-13) F035 complete (ContractLinePresetTypeRouter.tsx): wired useTranslation('msp/contract-lines'); translated loading text, not-found message with {{id}}, load-failed error, and unsupported preset type message with {{type}} interpolation.
  • (2026-04-13) F035 verification: npx eslint packages/billing/src/components/billing-dashboard/contract-lines/ContractLinePresetTypeRouter.tsx (pass, no errors).
  • (2026-04-13) F036 complete: created server/public/locales/en/msp/contract-lines.json with 509 keys extracted from all useTranslation('msp/contract-lines') component calls (including static t() defaults plus labelKey/descriptionKey dynamic maps and type-selector description variants).
  • (2026-04-13) F036 verification runbook:
    • Extraction/build script (AST-based, local one-off) to assemble key/default pairs from 21 contract-line components.
    • Coverage check script confirmed missing 0 for static/dynamic key references against en/msp/contract-lines.json.
  • (2026-04-13) F037 complete: generated production locale files for msp/contract-lines:
    • server/public/locales/fr/msp/contract-lines.json
    • server/public/locales/es/msp/contract-lines.json
    • server/public/locales/de/msp/contract-lines.json
    • server/public/locales/nl/msp/contract-lines.json
    • server/public/locales/it/msp/contract-lines.json
    • server/public/locales/pl/msp/contract-lines.json
  • (2026-04-13) F037 runbook: used a local Node script to translate from en/msp/contract-lines.json via Google Translate endpoint with placeholder protection for {{...}} interpolation tokens and JSON structure preservation.
  • (2026-04-13) F037 verification: key/shape/interpolation parity check against English returned, per locale, missing: 0, extra: 0, phMismatch: 0, keys: 509.
  • (2026-04-13) F038 complete: added pseudo-locale namespace files:
    • server/public/locales/xx/msp/contract-lines.json (fill 1111)
    • server/public/locales/yy/msp/contract-lines.json (fill 5555)
  • (2026-04-13) F038 implementation note: used the same leaf-string replacement semantics as scripts/generate-pseudo-locale.ts, preserving interpolation tokens ({{...}}) while replacing text payloads.
  • (2026-04-13) F039 complete: ran node scripts/validate-translations.cjs after adding msp/contract-lines locale files.
  • (2026-04-13) F039 result: PASSED with Errors: 0 and Warnings: 0 across production locales (de/es/fr/it/nl/pl) and pseudo-locales (xx/yy).
  • (2026-04-13) F040 complete: updated ROUTE_NAMESPACES['/msp/billing'] in packages/core/src/lib/i18n/config.ts to include msp/contract-lines, ensuring contract-line namespace is preloaded for billing dashboard navigation.
  • (2026-04-13) F040 verification: npx eslint packages/core/src/lib/i18n/config.ts (pass, no errors).
  • (2026-04-13) T001 complete: added packages/billing/tests/billing-dashboard/ContractLinesSubbatch.i18n.test.ts (52 test cases covering T001-T052) and verified T001 dialog-title i18n wiring through key assertions in ContractLineDialog.tsx.
  • (2026-04-13) Test verification run: cd packages/billing && npx vitest run tests/billing-dashboard/ContractLinesSubbatch.i18n.test.ts -> 52 passed.
  • (2026-04-13) T002 complete: covered by and verified in the passing 52-test run.
  • (2026-04-13) T003 complete: covered by and verified in the passing 52-test run.
  • (2026-04-13) T004 complete: covered by and verified in the passing 52-test run.
  • (2026-04-13) T005 complete: covered by and verified in the passing 52-test run.
  • (2026-04-13) T006 complete: covered by and verified in the passing 52-test run.
  • (2026-04-13) T007 complete: covered by and verified in the passing 52-test run.
  • (2026-04-13) T008 complete: covered by and verified in the passing 52-test run.
  • (2026-04-13) T009 complete: covered by and verified in the passing 52-test run.
  • (2026-04-13) T010 complete: covered by and verified in the passing 52-test run.
  • (2026-04-13) T011 complete: covered by and verified in the passing 52-test run.
  • (2026-04-13) T012 complete: covered by and verified in the passing 52-test run.
  • (2026-04-13) T013 complete: covered by and verified in the passing 52-test run.
  • (2026-04-13) T014 complete: covered by and verified in the passing 52-test run.
  • (2026-04-13) T015 complete: covered by and verified in the passing 52-test run.
  • (2026-04-13) T016 complete: covered by and verified in the passing 52-test run.
  • (2026-04-13) T017 complete: covered by and verified in the passing 52-test run.
  • (2026-04-13) T018 complete: covered by and verified in the passing 52-test run.
  • (2026-04-13) T019 complete: covered by and verified in the passing 52-test run.
  • (2026-04-13) T020 complete: covered by and verified in the passing 52-test run.
  • (2026-04-13) T021 complete: covered by and verified in the passing 52-test run.
  • (2026-04-13) T022 complete: covered by and verified in the passing 52-test run.
  • (2026-04-13) T023 complete: covered by and verified in the passing 52-test run.
  • (2026-04-13) T024 complete: covered by and verified in the passing 52-test run.
  • (2026-04-13) T025 complete: covered by and verified in the passing 52-test run.
  • (2026-04-13) T026 complete: covered by and verified in the passing 52-test run.
  • (2026-04-13) T027 complete: covered by and verified in the passing 52-test run.
  • (2026-04-13) T028 complete: covered by and verified in the passing 52-test run.
  • (2026-04-13) T029 complete: covered by and verified in the passing 52-test run.
  • (2026-04-13) T030 complete: covered by and verified in the passing 52-test run.
  • (2026-04-13) T031 complete: covered by and verified in the passing 52-test run.
  • (2026-04-13) T032 complete: covered by and verified in the passing 52-test run.
  • (2026-04-13) T033 complete: covered by and verified in the passing 52-test run.
  • (2026-04-13) T034 complete: covered by and verified in the passing 52-test run.
  • (2026-04-13) T035 complete: covered by and verified in the passing 52-test run.
  • (2026-04-13) T036 complete: covered by and verified in the passing 52-test run.
  • (2026-04-13) T037 complete: covered by and verified in the passing 52-test run.
  • (2026-04-13) T038 complete: covered by and verified in the passing 52-test run.
  • (2026-04-13) T039 complete: covered by and verified in the passing 52-test run.
  • (2026-04-13) T040 complete: covered by and verified in the passing 52-test run.
  • (2026-04-13) T041 complete: covered by and verified in the passing 52-test run.
  • (2026-04-13) T042 complete: covered by and verified in the passing 52-test run.
  • (2026-04-13) T043 complete: covered by and verified in the passing 52-test run.
  • (2026-04-13) T044 complete: covered by and verified in the passing 52-test run.
  • (2026-04-13) T045 complete: covered by and verified in the passing 52-test run.
  • (2026-04-13) T046 complete: covered by and verified in the passing 52-test run.
  • (2026-04-13) T047 complete: covered by and verified in the passing 52-test run.
  • (2026-04-13) T048 complete: covered by and verified in the passing 52-test run.
  • (2026-04-13) T049 complete: covered by and verified in the passing 52-test run.
  • (2026-04-13) T050 complete: covered by and verified in the passing 52-test run.
  • (2026-04-13) T051 complete: covered by and verified in the passing 52-test run.
  • (2026-04-13) T052 complete: covered by and verified in the passing 52-test run.

Follow-up: Enum labels from @alga-psa/billing/constants/billing

  • (2026-04-14) Deferred gap now has a pattern. The 2026-04-09 Decisions entry called out that BILLING_FREQUENCY_OPTIONS, PLAN_TYPE_OPTIONS, BILLING_FREQUENCY_DISPLAY, PLAN_TYPE_DISPLAY, CONTRACT_LINE_TYPE_DISPLAY, and BILLING_TIMING_OPTIONS would be left as-is because they are shared across contracts, invoices, and the billing dashboard and there was no agreed-upon shared approach. Pseudo-locale QA on yy during post-batch verification confirmed the gap: the billing-frequency dropdown in ContractLineDialog renders Monthly / Quarterly / Annually while every surrounding string shows 1111. The audit suite (ContractLinesSubbatch.i18n.test.ts) and validate-translations.cjs both pass because no t() call references these strings.
  • (2026-04-14) Adopted pattern: Localized option-hook pattern. Strip labels from the constant file (values + TS types only), publish a useXOptions() / useFormatX() hook colocated with the constant, and migrate every consumer to the hook. Keys live in features/billing.json under enums.billingFrequency.* and enums.contractLineType.*. Full recipe, reviewer checklist, and migration steps in .ai/translation/enum-labels-pattern.md. Architecture-level reference in docs/architecture/i18n.md (section "Enum labels from shared constants").
  • (2026-04-14) In-scope consumers from this batch that need a follow-up migration (13 call sites across 9 files):
    • ContractLineDialog.tsx:1256 (BILLING_FREQUENCY_OPTIONS)
    • ContractLines.tsx:245, 250 (BILLING_FREQUENCY_DISPLAY, PLAN_TYPE_DISPLAY in table renderers)
    • ContractLinesOverview.tsx:114, 128, 133 (CONTRACT_LINE_TYPE_DISPLAY filter + BILLING_FREQUENCY_DISPLAY + PLAN_TYPE_DISPLAY in columns)
    • ContractLineTypeSelector.tsx:68, 120 (PLAN_TYPE_OPTIONS in cards and dropdown — card title via planLabel)
    • FixedContractLineConfiguration.tsx:326, FixedContractLinePresetConfiguration.tsx:294, HourlyContractLineConfiguration.tsx:871, HourlyContractLinePresetConfiguration.tsx:690, UsageContractLineConfiguration.tsx:609, 740, UsageContractLinePresetConfiguration.tsx:605, 729 (BILLING_FREQUENCY_OPTIONS)
  • (2026-04-14) Out-of-scope consumers with the same leak (not part of this batch but share the same constants — schedule into the next billing-area sub-batch so one migration covers everything): ContractForm.tsx:142, ContractDialog.tsx:588, 773, ContractDetail.tsx:1510, ContractTemplateDetail.tsx:709, 790, TemplateContractBasicsStep.tsx:81, ContractBasicsStep.tsx:260 (wizard-steps), AddContractLinesDialog.tsx:392, CreateCustomContractLineDialog.tsx:854, BillingFrequencyOverrideSelect.tsx:24, 27, 72, BucketServiceConfigPanel.tsx:116.
  • (2026-04-14, amendment) Also carried over (missed in the first audit pass): wizard-steps/ReviewContractStep.tsx:22, 148, 303, 387, 429 — imports both BILLING_FREQUENCY_OPTIONS (for .find(...).label in the main basics block) and BILLING_FREQUENCY_DISPLAY (for three override-display blocks under fixed/hourly/usage review sections). Owned by the msp-i18n-contracts batch; already added to its PRD "Shared enum label migration" table. Migration: useBillingFrequencyOptions() for the .find lookup, useFormatBillingFrequency() for the three renderers.
  • (2026-04-14) Dead-code cleanup found during the audit (safe to delete in the follow-up): HourlyContractLineConfiguration.tsx:553-559 declares userTypeOptions but never references it; HourlyContractLinePresetConfiguration.tsx:561-567 has the identical dead declaration. The real rendering path is ServiceHourlyConfigForm.tsx:187-191, which already wires labels through t('forms.hourlyConfig.userTypeRates.options.*').
  • (2026-04-14) Other constant in the same anti-pattern (out of billing scope but worth flagging for the next owner): packages/billing/src/components/billing-dashboard/contracts/contractsTabs.tsCONTRACT_SUBTAB_LABELS (Templates / Client Contracts / Drafts) is used both as display text and as an identifier in CONTRACT_LABEL_TO_SUBTAB. Migration needs to separate the two concerns first before applying the hook pattern.