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

178 lines
27 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Scratchpad — MSP i18n: Billing Dashboard Sub-batch
- Plan slug: `2026-04-09-msp-i18n-billing-dashboard`
- Created: `2026-04-09`
## What This Is
Create a new `msp/billing` namespace and wire 17 top-level billing dashboard components that
currently have zero i18n coverage. These are the "shell" components -- the main dashboard
container, overview, usage tracking, reconciliation, line items, contract line services,
accounting exports, and template rendering. Sub-batches for contracts, quotes, invoicing,
credits, and service catalog are handled in separate plans.
## Decisions
- **(2026-04-09)** New namespace `msp/billing` rather than extending `features/billing`.
Rationale: `features/billing` is used by client-portal components and loaded on
`/client-portal/billing`. MSP billing has a much larger surface area with different
terminology (reconciliation, contract lines, accounting exports, etc.). Keeping them
separate avoids loading ~475 MSP-only keys on the client portal.
- **(2026-04-09)** Use `t('key', { defaultValue: 'English fallback' })` pattern consistently
across all components. This matches the established pattern in the codebase.
- **(2026-04-09)** Import pattern: `import { useTranslation } from '@alga-psa/ui/lib/i18n/client'`
and `import { useFormatters } from '@alga-psa/ui/lib/i18n/client'` for currency/date formatting.
- **(2026-04-09)** `billingTabsConfig.ts` is a plain TypeScript file (not a React component),
so it cannot call `useTranslation()` directly. Options: (a) store translation keys in the
config and translate in the consuming component, or (b) convert to a hook that returns
translated definitions. Option (a) is simpler and consistent with how the config is used.
Decision: store `labelKey` alongside `label` in the config, translate in the tab rendering
component using `t(tab.labelKey, { defaultValue: tab.label })`.
- **(2026-04-09)** `TemplateRendererCore.ts` is a pure function (not a React component), so
it cannot use hooks. The strings it outputs (`N/A`, `Unknown value`, `No data for list`,
`Uncategorized`) appear in rendered invoice HTML, not in the dashboard UI chrome. These
should be audited but likely marked as N/A for this batch -- invoice content localization
is a separate concern.
- **(2026-04-09)** `TemplateRenderer.tsx` has 3-4 small strings (loading, error, empty state)
that are proper UI chrome and should be translated.
## Discoveries / Constraints
- **(2026-04-09)** Current `ROUTE_NAMESPACES['/msp/billing']` loads
`['common', 'msp/core', 'features/billing', 'msp/reports']`. Adding `'msp/billing'` to
this array is the only config change needed.
- **(2026-04-10)** `server/public/locales/en/msp/billing.json` was created as a broad foundational namespace with shared vocabulary across dashboard, overview, reconciliation, discrepancy, usage, line-item, services, exports, template designer, and quantity-dialog surfaces. It intentionally front-loads reusable labels so later component wiring can reference stable keys instead of inventing ad hoc per-file strings.
- **(2026-04-10)** Locale generation can be bootstrapped from the English namespace using `translate.googleapis.com` while preserving `{{interpolation}}` tokens. This is good enough for parity work, but repeated billing vocabulary still needs spot-auditing because machine translation is not domain-aware (for example, quote/billing terminology can drift).
- **(2026-04-10)** A temporary smoke-test invocation of the locale generator created an unintended `server/public/locales/--help` directory. This was removed before validation; no tracked files depended on it.
- **(2026-04-09)** `ReconciliationResolution.tsx` (1129 LOC) is the largest file and has
~80 user-visible strings spanning 3 wizard steps, resolution options, four-eyes approval
flow, and a confirmation dialog. Split into 3 features (F020-F022).
- **(2026-04-10)** `ReconciliationResolution.tsx` can reuse the already-seeded `reconciliation.steps.*`, `reconciliation.sections.resolutionOptions`, and `reconciliation.resolutionTypes.*` keys without expanding the locale surface. That keeps the first reconciliation wiring pass focused and avoids immediate locale-file churn after F009 validation.
- **(2026-04-09)** `DiscrepancyDetail.tsx` (921 LOC) has ~70 strings and shares many labels
with ReconciliationResolution (balance comparison, issue types, recommended fix text).
Key reuse within the namespace will reduce total key count. Split into 3 features (F030-F032).
- **(2026-04-10)** `DiscrepancyDetail.tsx` can share a large chunk of terminology with the reconciliation wizard, but it also has its own `discrepancy.*` card/field/status groups. The first pass is mostly shell labeling; the tab-heavy table content is the riskier part and is better split into separate commits.
- **(2026-04-09)** `LineItem.tsx` (514 LOC) has complex conditional rendering for regular
items vs discounts (percentage vs fixed). The collapsed/expanded states have different
label sets. ~40 strings total, split into 2 features (F050-F051).
- **(2026-04-09)** `FixedContractLineServicesList.tsx` and `FixedContractLinePresetServicesList.tsx`
share nearly identical table structures and add-services UI. Many keys can be shared
under `contractLineServices.*` group. Preset-specific keys (unsaved changes, save/reset)
go under `presetServices.*`.
- **(2026-04-09)** `AccountingExportsTab.tsx` uses `react-hot-toast` (not `useToast()`) for
toast messages. The `toast.success('...')` calls should be wrapped with `t()`.
- **(2026-04-09)** `Overview.tsx` metric cards use a `MetricCard` component that accepts
`title` and `subtitle` props as strings. These need to become `t()` calls at the
call site, not inside MetricCard itself (MetricCard is a generic presentational component).
- **(2026-04-10)** `UsageTracking.tsx` already had a clean separation between list-shell strings and dialog/toast strings. That makes it safe to split the i18n pass into F017 (table/filter shell) and F018 (dialog, guidance, toasts) without touching locale structure in between.
- **(2026-04-10)** `LineItem.tsx` can be split cleanly between content labels/summary strings and the remaining chrome-only strings. The existing `lineItem.*` locale group already covers both halves, so F019/F020 do not need additional locale keys unless a hidden string surfaces during test coverage.
- **(2026-04-10)** `FixedContractLineServicesList.tsx` already has a complete `contractLineServices.*` locale group for both the table shell and add-services drawer copy. The only extra reuse needed is `common.notAvailable` / `common.openMenu` for fallback and accessibility labels, so F021/F022 can stay locale-neutral unless a new error state appears.
- **(2026-04-10)** `AccountingExportsTab.tsx` already has enough `accountingExports.*` keys for the card shell and both dialogs, but it does not yet have explicit status-label keys for backend values like `pending`, `delivered`, and `needs_attention`. Those raw status codes are still visible after the first shell pass and need a follow-up before the PRD is truly done.
- **(2026-04-10)** Added discovered follow-up items `F024A` / `T020A` for accounting-export status labels. Rationale: the PRD explicitly calls out status labels, but the original checklist only covered shell/dialog chrome and the namespace did not yet include `accountingExports.status.*` keys.
- **(2026-04-09)** `ContractsHub.tsx` is small (77 LOC) but renders tab labels that should
use `msp/billing` namespace for consistency with the billing dashboard.
- **(2026-04-09)** `PropertyEditor.tsx` and `ConditionalRuleManager.tsx` are part of the
invoice template designer. They have few strings (~7 and ~6 respectively) but are
user-facing UI chrome that should be translated.
## Gotchas
- **billingTabsConfig.ts tab labels** -- these are consumed by `BillingDashboard.tsx` which
renders them via Radix Tabs. The tab definitions are also used in sidebar navigation.
Need to verify that all consumers of `billingTabDefinitions` handle the translation key
pattern correctly.
- **FixedContractLineServicesList Default Rate tooltip** -- uses `<Tooltip>` component with
complex content. The tooltip text is a sentence about service rate allocation. Translate
the full sentence as a single key rather than composing from fragments.
- **ReconciliationResolution STEPS constant** -- defined at module scope outside the component.
Either (a) convert to a function that accepts `t`, or (b) define translated labels inside
the component and map from STEPS. Option (b) avoids changing the STEPS interface.
- **formatCurrency / formatDateTime** -- these already use locale-aware formatting via
`@alga-psa/core`. No need to translate their output; they handle locale internally.
- **Discount type options in LineItem.tsx** -- `discountTypeOptions` is defined at module
scope as a constant array. Same pattern as STEPS -- translate in-component.
- **BILLING_METHOD_OPTIONS** in FixedContractLineServicesList and FixedContractLinePresetServicesList
-- defined at module scope. Same pattern: translate labels in-component or use `t()` in
the render callback.
- **(2026-04-10)** The initial English namespace includes some forward-looking keys for later features/tests. As component wiring lands, keep locale parity by updating the real locales and pseudo-locales in lockstep rather than creating one-off English-only keys.
## Key Count Estimate
| Group | Est. Keys |
|-------|-----------|
| dashboard (title, beta, tabs) | ~20 |
| overview (metrics, features, catalog) | ~40 |
| reconciliation (stepper, resolution, approval, confirmation) | ~55 |
| discrepancy (status, tables, issue detail, dialog) | ~50 |
| recommendedFix (panels, dialogs, impact) | ~30 |
| usage (table, form, filters, toasts) | ~40 |
| lineItem (fields, discounts, summary) | ~35 |
| contractLineServices (table, add, actions) | ~30 |
| presetServices (table, unsaved, save/reset) | ~20 |
| accountingExports (table, dialogs) | ~35 |
| templateRenderer/Designer (loading, labels) | ~15 |
| contractsHub (heading, tabs) | ~5 |
| editQuantityDialog (title, validation, buttons) | ~10 |
| **Total** | **~385** |
After key reuse (shared labels like Cancel, Save, Error, etc. from `common` namespace),
the actual `msp/billing.json` key count will likely be ~350-380 unique keys.
## Progress Log
- **(2026-04-10) F001 complete** -- Added `server/public/locales/en/msp/billing.json` with 14 top-level groups (`common`, `dashboard`, `overview`, `reconciliation`, `discrepancy`, `recommendedFix`, `usage`, `lineItem`, `contractLineServices`, `presetServices`, `accountingExports`, `templateRenderer`, `templateDesigner`, `contractsHub`, `editQuantityDialog`, `templateRendererCore`). Verified the file parses with `node -e "JSON.parse(...)"`. This gives the batch a stable namespace before locale generation and component wiring.
- **(2026-04-10) F002 complete** -- Generated `server/public/locales/fr/msp/billing.json` from the English source with placeholder preservation and verified it parses. The first pass is machine-generated; keep an eye on MSP-specific wording during later UI smoke tests.
- **(2026-04-10) F003 complete** -- Generated `server/public/locales/es/msp/billing.json` with the same placeholder-safe pipeline and verified it parses. Locale generation is now quick enough to finish the remaining real locales before component wiring.
- **(2026-04-10) F004 complete** -- Generated `server/public/locales/de/msp/billing.json` and verified it parses. The machine pass works structurally; wording cleanup remains a later QA item, especially for action verbs and billing-domain nouns.
- **(2026-04-10) F005 complete** -- Generated `server/public/locales/nl/msp/billing.json` and verified it parses. The translation endpoint threw one transient 500 during generation; resuming from the on-disk cache completed the locale without losing prior progress.
- **(2026-04-10) F006 complete** -- Generated `server/public/locales/it/msp/billing.json` and verified it parses. The explicit Italian accent audit still needs the global validation pass, but the locale file is now present and structurally correct.
- **(2026-04-10) F007 complete** -- Generated `server/public/locales/pl/msp/billing.json` and verified it parses. At this point all real-language locale files for `msp/billing` exist; pseudo-locales and parity validation are next.
- **(2026-04-10) F008 complete** -- Ran `node scripts/generate-pseudo-locales.cjs`, which regenerated `server/public/locales/xx/msp/billing.json` and `server/public/locales/yy/msp/billing.json` from the English source. Verified both pseudo-locale files now exist.
- **(2026-04-10) F009 complete** -- Ran `node scripts/validate-translations.cjs` after cleaning up the stray `--help` locale directory. Validation passed with 0 errors / 0 warnings across `de`, `es`, `fr`, `it`, `nl`, `pl`, `xx`, and `yy`.
- **(2026-04-10) F010 complete** -- Updated `packages/core/src/lib/i18n/config.ts` so `ROUTE_NAMESPACES['/msp/billing']` now loads `msp/billing` alongside `common`, `msp/core`, `features/billing`, and `msp/reports`. This is the only route-config change needed for this batch.
- **(2026-04-10) F011 complete** -- Wired `useTranslation('msp/billing')` into `packages/billing/src/components/billing-dashboard/ReconciliationResolution.tsx` for the stepper labels and the three resolution-type options. The step array now derives labels from `t()` at render time instead of relying on the module-scope English constants.
- **(2026-04-10) F012 complete** -- Continued the `ReconciliationResolution.tsx` pass by translating the discrepancy detail labels (`Client`, `Status`, `Detected`, `Issue Type`), the balance comparison labels, and the two issue-type titles. This kept the commit aligned with the existing `reconciliation.fields.*`, `reconciliation.sections.*`, `reconciliation.status.*`, and `reconciliation.issueTypes.*` key groups.
- **(2026-04-10) F013 complete** -- Finished the main reconciliation wizard shell: four-eyes approval copy, approval verification UI, correction summary labels, confirmation-step summary text, completion dialog copy, and user-facing error messages now read from `msp/billing`. Reused existing namespace keys throughout, so no locale-file regeneration was needed after F009.
- **(2026-04-10) F014 complete** -- Wired `useTranslation('msp/billing')` into `packages/billing/src/components/billing-dashboard/DiscrepancyDetail.tsx` for the back navigation text, status badges, discrepancy detail labels, issue-type labels, and balance comparison card labels.
- **(2026-04-10) F015 complete** -- Continued `DiscrepancyDetail.tsx` by translating the transaction-history tab labels, expanded transaction metadata labels, the credit-tracking tab labels, and the credit-entry detail labels/status text. No new locale keys were needed because `discrepancy.tabs.*`, `discrepancy.cards.*`, `discrepancy.fields.*`, `discrepancy.status.*`, and `discrepancy.empty.*` were already seeded in F001.
- **(2026-04-10) F016 complete** -- Finished `DiscrepancyDetail.tsx` by translating the issue-details tab, recommended-fix copy, credit-applications table headings, resolve-discrepancy dialog labels, and the remaining empty/error states. This closes out the discrepancy detail screen without expanding the locale schema beyond the keys already seeded in F001.
- **(2026-04-10) F017 complete** -- Wired `useTranslation('msp/billing')` into `packages/billing/src/components/billing-dashboard/UsageTracking.tsx` for the bucket overview title, usage table headers, contract-line summary text, filter labels/placeholders, loading state, and row action menu labels.
- **(2026-04-10) F018 complete** -- Finished `UsageTracking.tsx` by translating the add/edit dialog labels, contract-line selector guidance/placeholder copy, create-update-delete toasts, and the delete confirmation dialog. This closes out the usage-tracking screen without introducing new locale keys after the foundational namespace work.
- **(2026-04-10) F019 complete** -- Wired `useTranslation('msp/billing')` into `packages/billing/src/components/billing-dashboard/LineItem.tsx` for the regular field labels, discount field labels, collapsed summary strings, subtotal/discount summaries, and the discount-description placeholder. The file reused the seeded `lineItem.collapsed.*`, `lineItem.fields.*`, `lineItem.placeholders.*`, and `lineItem.summary.*` keys, so locale regeneration was not needed.
- **(2026-04-10) F020 complete** -- Finished `LineItem.tsx` by translating the expanded header labels (`Discount`, `Item {{number}}`, `Marked for removal`), action buttons, discount type select options, the `Entire Invoice` target option, and the percentage-discount “calculated on save” hint. This closes out the top-level line-item editor without expanding the `lineItem.*` locale group.
- **(2026-04-10) F021 complete** -- Wired `useTranslation('msp/billing')` into `packages/billing/src/components/billing-dashboard/FixedContractLineServicesList.tsx` for the associated-services table headers, billing-method labels, Product/Service badges, default-rate tooltip, action-menu labels, missing-price cell text, and the small accessibility/fallback strings (`Open menu`, `N/A`, `Unknown Service`). No locale regeneration was needed because the seeded `contractLineServices.*` keys plus `common.*` already covered the table shell.
- **(2026-04-10) F022 complete** -- Finished `FixedContractLineServicesList.tsx` by translating the load/add/remove error messages, loading/empty states, add-services section heading, service metadata rows, product custom-rate label, count-aware add button, and the `Unknown Service` fallback passed into the quantity dialog. This closes out the fixed contract-line services list without adding new locale keys beyond the original `contractLineServices.*` plan.
- **(2026-04-10) F023 complete** -- Wired `useTranslation('msp/billing')` into `packages/billing/src/components/billing-dashboard/accounting/AccountingExportsTab.tsx` for the card title/description, batch table headers, outer action buttons, list loading/empty states, and the list-side execute/load-batches feedback messages. This leaves the create/detail dialogs plus status-label normalization for follow-up commits.
- **(2026-04-10) F024 complete** -- Finished `AccountingExportsTab.tsx` by translating the new-export dialog labels/placeholders/buttons, the batch-detail dialog title/field labels/states, and the create/load-detail feedback messages. The remaining accounting-exports gap is the raw backend status-code display, which is now tracked explicitly as `F024A`.
- **(2026-04-10) F024A complete** -- Added `accountingExports.status.*` keys to the English and six real-language locale files, regenerated pseudo-locales, and mapped `AccountingExportsTab.tsx` batch statuses (`pending`, `validating`, `ready`, `delivered`, `posted`, `failed`, `cancelled`, `needs_attention`) through `t()`. Re-ran `node scripts/generate-pseudo-locales.cjs` and `node scripts/validate-translations.cjs`; validation passed with 0 errors / 0 warnings.
- **(2026-04-10) F025 complete** -- Wired `useTranslation('msp/billing')` into `packages/billing/src/components/billing-dashboard/FixedContractLinePresetServicesList.tsx` for the preset table headers, billing-method labels, default-rate tooltip, row action label, unsaved-changes warning, loading text, add-services section title/button, and save/reset button states. Reused `presetServices.*`, `contractLineServices.billingMethods.*`, and `common.*` without expanding the locale schema.
- **(2026-04-10) F026 complete** -- Finished `FixedContractLinePresetServicesList.tsx` by translating the preset empty states, add-list service metadata row, and the navigate-away confirmation dialog. The preset save/load error alerts now flow through translated default messages introduced in the prior pass, so this closes out the preset services manager without new locale keys.
- **(2026-04-10) F027 complete** -- Wired `useTranslation('msp/billing')` into `packages/billing/src/components/billing-dashboard/RecommendedFixPanel.tsx` for the card title, panel headings, issue-type-specific descriptions, per-fix button labels, and the field labels embedded in the recommended-fix summaries. Existing `recommendedFix.*`, `reconciliation.fields.*`, and `discrepancy.fields.*` keys were sufficient, so no locale-file update was needed.
- **(2026-04-10) F028 complete** -- Finished `RecommendedFixPanel.tsx` by translating the fix-dialog titles/descriptions, adjustment amount and notes fields, impact-summary labels, resolved-state copy, and dialog/apply error states. This closes out the recommended-fix workflow without further locale changes because the seeded `recommendedFix.dialog.*`, `recommendedFix.impactSummary.*`, `recommendedFix.resolved.*`, and `recommendedFix.errors.*` groups already existed.
- **(2026-04-10) F029 complete** -- Wired `useTranslation('msp/billing')` into `packages/billing/src/components/billing-dashboard/Overview.tsx` for the seven metric card titles/subtitles, the Monthly Activity section, and the Service Catalog Management quick-access card. This kept the first Overview pass aligned with the seeded `overview.metrics.*` and `overview.sections.*` keys.
- **(2026-04-10) F030 complete** -- Finished `Overview.tsx` by translating the feature-card grid, the destructive load-error alert, the small metric fallback states (`...`, `Error`, `0`, `0 hours`), and the development-only debug labels. No locale updates were needed because the seeded `overview.features.*`, `overview.errors.*`, `overview.states.*`, and `overview.debug.*` groups already covered the remaining strings.
- **(2026-04-10) F031 complete** -- Wired `useTranslation('msp/billing')` into `packages/billing/src/components/billing-dashboard/BillingDashboard.tsx` for the page title, beta banner, error prefix, quote-templates heading, and the back-to-preset navigation text. The component still does not render the tab labels from `billingTabsConfig`, so the separate F033 follow-up remains necessary.
- **(2026-04-10) F032 complete** -- Wired `useTranslation('msp/billing')` into `packages/billing/src/components/billing-dashboard/EditContractLineServiceQuantityDialog.tsx` for the interpolated dialog title, quantity label, validation messages, save error fallback, cancel/save buttons, and saving indicator text. No locale changes were needed because `editQuantityDialog.*` already covered the entire dialog surface.
- **(2026-04-10) F033 complete** -- Added `labelKey` to every entry in `packages/billing/src/components/billing-dashboard/billingTabsConfig.ts` and translated the config in `BillingDashboard.tsx` with `t(tab.labelKey, { defaultValue: tab.label })`. `BillingDashboard` still does not render a local `Tabs.List`, but the route now consumes a translated tab-definition model rather than raw English labels.
- **(2026-04-10) F034 complete** -- Wired `useTranslation('msp/billing')` into `packages/billing/src/components/billing-dashboard/TemplateRenderer.tsx` for the loading text, destructive error prefix, and empty-state message shown before an invoice/template pair is selected. No locale updates were needed because `templateRenderer.*` already existed.
- **(2026-04-10) F035 complete** -- Wired `useTranslation('msp/billing')` into `packages/billing/src/components/billing-dashboard/PropertyEditor.tsx` for the field labels, “Select a field” option, and the generated column/row size captions. The existing `templateDesigner.propertyEditor.*` keys covered the inspector without locale churn.
- **(2026-04-10) F036 complete** -- Wired `useTranslation('msp/billing')` into `packages/billing/src/components/billing-dashboard/ConditionalRuleManager.tsx` for the heading, action select options, text-input placeholders, and add-rule button label. The existing `templateDesigner.conditionalRules.*` keys fully covered the component.
- **(2026-04-10) F037 complete** -- Wired `useTranslation('msp/billing')` into `packages/billing/src/components/billing-dashboard/ContractsHub.tsx` for the hub heading and the two sub-tab labels. This closes out the contracts hub chrome with the existing `contractsHub.*` keys.
- **(2026-04-10) F038 complete** -- Audited `packages/billing/src/components/billing-dashboard/TemplateRendererCore.ts` and confirmed its fallback strings (`No data for list`, `Uncategorized`, `N/A`, `Unknown value`) are emitted into generated invoice HTML rather than the billing dashboard shell. Added code comments documenting that boundary instead of forcing dashboard-namespace translation into the pure renderer.
- **(2026-04-10) T001 complete** -- Re-ran `node scripts/generate-pseudo-locales.cjs` and `node scripts/validate-translations.cjs` after the later accounting-export status-key expansion and final feature wiring. Validation still passed with 0 errors / 0 warnings across `de`, `es`, `fr`, `it`, `nl`, `pl`, `xx`, and `yy`, so locale parity remains intact after the full feature set.
- **(2026-04-10) T002 complete** -- Added `packages/billing/tests/billing-dashboard/ReconciliationResolution.i18n.test.ts` and verified, under `packages/billing`s Vitest config, that the stepper labels are wired through `msp/billing` and backed by pseudo-locale keys. Command used: `npm exec -- vitest --root packages/billing --config vitest.config.ts run tests/billing-dashboard/ReconciliationResolution.i18n.test.ts`.
- **(2026-04-10) T003 complete** -- Extended the same ReconciliationResolution audit test to cover the three resolution-option labels (`Recommended Fix`, `Custom Correction`, `No Action Required`) and verified the updated file passes under the billing package Vitest config.
- **(2026-04-10) T004 complete** -- Extended the ReconciliationResolution audit to cover the translated balance-comparison labels and the four-eyes approval copy (`requiredTitle`, `requiredDescription`, approver fields, verification code, verified badge title). The billing-package Vitest run remained green.
- **(2026-04-10) T005 complete** -- Extended the ReconciliationResolution audit to cover the confirmation-step copy (`importantTitle`, `importantDescription`, confirm/close buttons, thank-you title) and the key reconciliation error messages. The shared audit file still passed under the billing-package Vitest config.
- **(2026-04-10) T006 complete** -- Added a pseudo-locale coverage assertion to the same ReconciliationResolution audit, verifying that representative step, resolution-type, four-eyes, and confirmation keys are present in `server/public/locales/xx/msp/billing.json` rather than falling back to raw English.
## Runbook
- `node -e "JSON.parse(require('fs').readFileSync('server/public/locales/en/msp/billing.json','utf8')); console.log('ok')"`
- `node - <<'NODE' ... generate translated locale from en/msp/billing.json via translate.googleapis.com while preserving {{placeholders}} ... NODE`
- `node scripts/generate-pseudo-locales.cjs`
- `rm -rf server/public/locales/--help && node scripts/validate-translations.cjs`
- `npm exec eslint -- packages/billing/src/components/billing-dashboard/LineItem.tsx`
- `npm exec eslint -- packages/billing/src/components/billing-dashboard/FixedContractLineServicesList.tsx`
- `npm exec eslint -- packages/billing/src/components/billing-dashboard/accounting/AccountingExportsTab.tsx`