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
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
274 lines
19 KiB
Markdown
274 lines
19 KiB
Markdown
# Scratchpad — MSP i18n: Credits Sub-batch
|
||
|
||
- Plan slug: `2026-04-09-msp-i18n-credits`
|
||
- Created: `2026-04-09`
|
||
- Last synced to codebase: `2026-04-17`
|
||
|
||
## Status Recheck (2026-04-17)
|
||
|
||
**Correction (2026-04-17 re-read):** an earlier version of this section said "Still 0% implemented" — that was based on stale `ls` output against `origin/main`. The **actual** state on branch `i18n/billing_credits` (32 commits ahead of origin/main) is:
|
||
|
||
- `features.json`: **22/22** features implemented.
|
||
- `tests.json`: **31/31** tests implemented.
|
||
- `server/public/locales/en/msp/credits.json` **exists** (plus de/es/fr/it/nl/pl/xx/yy — 9 locale files total, 185 lines each).
|
||
- All 8 client components wired with `useTranslation('msp/credits')` (except `CreditsPage.tsx` which delegates to the new `CreditsPageClient.tsx` wrapper per the server-component decision).
|
||
- New file: `packages/billing/src/components/credits/CreditsPageClient.tsx` (350 LOC) implementing the recommended server→client split.
|
||
- `/msp/billing/credits` added to `ROUTE_NAMESPACES` in `packages/core/src/lib/i18n/config.ts`.
|
||
- Seven new Vitest suites under `packages/billing/tests/` (`CreditsPage`, `CreditControls`, `CreditManagement`, `CreditReconciliation`, `CreditApplicationExpiration`, `CreditsLocaleSmoke`, `creditsNamespaceAndRoute`).
|
||
|
||
Status on this branch: **implementation complete, pending merge to `main`**. On `origin/main` the plan is still 0%.
|
||
|
||
### Adjacent work (context)
|
||
|
||
| Change | Impact |
|
||
|---|---|
|
||
| Billing-settings plan (`2026-04-09-msp-i18n-billing-settings`) shipped — `CreditExpirationSettings` lives in `msp/billing-settings`, not `msp/credits`. | No file overlap; the dashboard-side `CreditExpirationInfo.tsx` + `CreditExpirationModificationDialog.tsx` are covered here. Terminology mirrored across namespaces. |
|
||
| Enum-labels pattern adopted 2026-04-14 (`.ai/translation/enum-labels-pattern.md`). | No shared billing-frequency enum rendering in the 10 credit components — no migration needed. |
|
||
|
||
---
|
||
|
||
## What This Is
|
||
|
||
Greenfield i18n namespace creation + wiring for 10 credit management components. Unlike the
|
||
tickets migration (which extended an existing 147-key namespace), this batch creates
|
||
`msp/credits.json` from scratch and wires all components for the first time.
|
||
|
||
## Decisions
|
||
|
||
- **(2026-04-09)** Create a dedicated `msp/credits` namespace rather than adding to
|
||
`features/billing.json`. Rationale: credit management is MSP-only (no client-portal
|
||
equivalent), and the billing namespace already covers invoicing/plans. ~80 keys warrants
|
||
its own file.
|
||
- **(2026-04-09)** `CreditsPage.tsx` is a server component. Strategy: create a thin
|
||
`CreditsPageClient.tsx` wrapper that calls `useTranslation('msp/credits')` and passes
|
||
translated strings down. The server component fetches data; the client component handles
|
||
i18n. This matches the pattern used elsewhere in the codebase.
|
||
- **(2026-04-09)** `CreditsTabs.tsx` has zero user-visible strings (tab labels come from
|
||
props). Skip it entirely.
|
||
- **(2026-04-09)** `actions.ts` is a server action file. The error strings like
|
||
`'Authentication required'` and `'Transfer amount must be greater than zero'` do surface
|
||
to callers via the `{ success: false, error: '...' }` return shape. However, these are
|
||
consumed by client components that can translate them at the display layer. Leave
|
||
`actions.ts` untouched; translate error display in consuming components.
|
||
- **(2026-04-09)** Use `t('key', { defaultValue: 'English fallback' })` pattern per project
|
||
convention. This ensures English renders if translations are missing.
|
||
- **(2026-04-09)** Currency amounts stay formatted via `formatCurrency()` (which already
|
||
handles locale). Dates stay formatted via `toLocaleDateString()` or `formatDateOnly()`.
|
||
Do NOT wrap these in `t()`.
|
||
- **(2026-04-09)** Recharts `name` props in Bar/Pie components (e.g., "Credits Issued",
|
||
"Credits Applied") need translation since they appear in chart legends. The `formatter`
|
||
callbacks that use `formatCurrency()` do NOT need translation.
|
||
|
||
## Discoveries / Constraints
|
||
|
||
- **(2026-04-09)** `CreditManagement.tsx` (642 LOC) and `CreditReconciliation.tsx` (604 LOC)
|
||
are the largest components. CreditManagement has duplicate column definitions with
|
||
`CreditsPage.tsx` -- both define columns for the credits table with identical titles.
|
||
Consider extracting shared column title keys to avoid duplication.
|
||
- **(2026-04-09)** `CreditManagement.tsx` contains a `renderCreditContext()` helper function
|
||
with several context-specific labels (Lineage Missing, Transferred Recurring Credit,
|
||
Recurring Source, Financial Only). These are credit-specific domain terms that need
|
||
careful translation.
|
||
- **(2026-04-09)** `CreditReconciliation.tsx` uses `toast.success()` for the validation
|
||
result message. This needs interpolation:
|
||
`t('reconciliation.validationResult', { balanceCount: result.balanceDiscrepancyCount, trackingCount: result.missingTrackingCount + result.inconsistentTrackingCount })`.
|
||
- **(2026-04-09)** The chart time-range labels in `generateExpirationChartData` (< 7 days,
|
||
< 30 days, < 90 days, > 90 days) should be translated. These are passed as `name` to
|
||
Recharts data arrays and appear in tooltips/legends.
|
||
- **(2026-04-09)** The `/msp/billing/credits` route does not currently exist in
|
||
`ROUTE_NAMESPACES`. The existing `/msp/billing` entry loads
|
||
`['common', 'msp/core', 'features/billing', 'msp/reports']`. The new entry must be placed
|
||
so that longest-prefix matching resolves it before `/msp/billing`. Since object key order
|
||
matters for iteration, insert it immediately above the `/msp/billing` line.
|
||
- **(2026-04-09)** `CreditReconciliation.tsx` uses `useSession` from `next-auth/react` to
|
||
get the current user ID for the validation action. This is unrelated to i18n but notable
|
||
-- it does not use `withAuth`.
|
||
|
||
## Key Groups in msp/credits.json
|
||
|
||
| Group | Est. Keys | Source Components |
|
||
|-------|----------|-------------------|
|
||
| page | 6 | CreditsPage |
|
||
| columns | 10 | CreditsPage, CreditManagement |
|
||
| status | 5 | CreditsPage, CreditManagement |
|
||
| actions | 8 | CreditsPage, CreditManagement, AddCreditButton, BackButton |
|
||
| tabs | 3 | CreditsPage, CreditManagement |
|
||
| settings | 8 | CreditsPage |
|
||
| charts | 10 | CreditManagement, CreditsPage |
|
||
| stats | 4 | CreditManagement |
|
||
| management | 4 | CreditManagement |
|
||
| reconciliation | 25 | CreditReconciliation |
|
||
| application | 15 | CreditApplicationUI |
|
||
| expiration | 6 | CreditExpirationInfo |
|
||
| expirationDialog | 13 | CreditExpirationModificationDialog |
|
||
| context | 8 | CreditManagement |
|
||
| **Total** | **~125** | |
|
||
|
||
## Gotchas
|
||
|
||
1. **Server component**: `CreditsPage.tsx` is async and does not have `'use client'`. Cannot
|
||
call `useTranslation()` directly. Must wrap in a client component.
|
||
2. **Duplicate column definitions**: `CreditsPage.tsx` and `CreditManagement.tsx` both define
|
||
nearly identical column arrays with the same title strings. Use the same translation keys
|
||
for both to avoid drift.
|
||
3. **Recharts `name` prop**: Bar and Pie chart `name` strings appear in legends and tooltips.
|
||
These must be translated, but `name` is set at render time so `t()` works normally.
|
||
4. **Chart data labels**: The expiration chart data (`< 7 days`, `< 30 days`, etc.) is
|
||
generated in a function and stored in state. The `t()` call must happen at generation time
|
||
or the data must be regenerated when locale changes.
|
||
5. **Status badge HTML**: Status strings like "Expiring Soon (X days)" use JSX with
|
||
interpolation. Use `t('status.expiringSoon', { days: daysUntilExpiration })`.
|
||
6. **Toast message in CreditReconciliation**: The `toast.success()` call uses a template
|
||
literal. Replace with `t()` + interpolation.
|
||
7. **Month abbreviations in placeholder chart data**: The `placeholderCreditUsageData` array
|
||
uses English month names (Jan, Feb, etc.). These are placeholder data and will eventually
|
||
come from an analytics endpoint. For now, translate them as keys or leave as-is since
|
||
they are placeholder/demo data. Decision: translate them since they are user-visible.
|
||
|
||
## Implementation Log
|
||
|
||
- **(2026-04-17, F001)** Added the greenfield English namespace at
|
||
`server/public/locales/en/msp/credits.json`. The file now defines the 14 planned top-level
|
||
groups (`page`, `columns`, `status`, `actions`, `tabs`, `settings`, `charts`, `stats`,
|
||
`management`, `reconciliation`, `application`, `expiration`, `expirationDialog`,
|
||
`context`) and includes the shared table/status/chart/tab vocabulary needed by all 8 client
|
||
components plus the server `CreditsPage` wrapper. Included a few pragmatic extras inside the
|
||
planned groups rather than adding new top-level groups:
|
||
management load/empty states, reconciliation tab labels, short month labels for placeholder
|
||
charts, and generic application/expiration failure strings. This keeps later wiring stable
|
||
and avoids repeated locale-file churn.
|
||
- **(2026-04-17, F002)** Added `server/public/locales/fr/msp/credits.json` with full key parity
|
||
against English. Kept the same structure and interpolation tokens, translated the domain-
|
||
specific copy directly (credit reconciliation, recurring-lineage context, expiration flows),
|
||
and preserved the placeholder chart month subgroup so later pseudo-generation remains
|
||
mechanical.
|
||
- **(2026-04-17, F003)** Added `server/public/locales/es/msp/credits.json` with matching key
|
||
structure and preserved interpolation variables. Kept the recurring-credit context labels,
|
||
expiration flow copy, and reconciliation phrasing idiomatic enough for MSP billing rather
|
||
than literal English carry-over.
|
||
- **(2026-04-17, F004)** Added `server/public/locales/de/msp/credits.json`. Paid extra
|
||
attention to the reconciliation/dashboard nouns and the “financial artifact / recurring
|
||
service period” explanatory copy so the German file remains readable instead of over-literal.
|
||
- **(2026-04-17, F005)** Added `server/public/locales/nl/msp/credits.json` with the same key
|
||
shape. Kept the recurring-context explanation and reconciliation wording explicit, since
|
||
those are the easiest places for machine-like Dutch to leak in.
|
||
- **(2026-04-17, F006)** Added `server/public/locales/it/msp/credits.json` and deliberately
|
||
used accented/contracted Italian forms where they naturally occur (`più`, `Si è`, `Tutti gli`)
|
||
so the later accent audit is testing a real localized file rather than English-looking copy.
|
||
- **(2026-04-17, F007)** Added `server/public/locales/pl/msp/credits.json` with preserved
|
||
placeholders and the same month subgroup used by the chart placeholders. The recurring-
|
||
lineage copy needed a light polish to stay understandable in Polish while keeping the billing
|
||
domain meaning intact.
|
||
- **(2026-04-17, F008)** Ran `node scripts/generate-pseudo-locales.cjs` after the new English
|
||
namespace landed. The generator rebuilt `62` pseudo-locale files from `31` English sources
|
||
and created `server/public/locales/xx/msp/credits.json` plus
|
||
`server/public/locales/yy/msp/credits.json`. No hand-edits to pseudo-locales were made.
|
||
- **(2026-04-17, F009)** Ran `node scripts/validate-translations.cjs` immediately after pseudo-
|
||
locale generation. Result: `Errors: 0`, `Warnings: 0`, `PASSED` across `de/es/fr/it/nl/pl`
|
||
plus `xx/yy`. This confirmed key parity, interpolation preservation, pseudo fill patterns,
|
||
and the Italian file surviving the validator’s accent checks.
|
||
- **(2026-04-17, F010)** Added `'/msp/billing/credits'` to
|
||
`packages/core/src/lib/i18n/config.ts` immediately above the broader `/msp/billing` entry.
|
||
This preserves exact-match handling and longest-prefix fallback so the new page loads
|
||
`msp/credits` instead of inheriting only the general billing namespaces.
|
||
- **(2026-04-17, F011)** Replaced the old mixed server/client `CreditsPage.tsx` with a thin
|
||
server loader plus new client wrapper `packages/billing/src/components/credits/CreditsPageClient.tsx`.
|
||
The wrapper now owns `useTranslation('msp/credits')`, translated page/card headings, tab
|
||
labels, table column titles, status labels, and action-button copy while keeping currency and
|
||
date formatting intact. Also normalized the expired tab to derive its rows from the fetched
|
||
“all credits” dataset while preserving upstream fetch error states.
|
||
- **(2026-04-17, F012)** Finished the `CreditsPageClient` settings-summary translation pass.
|
||
The inline expiration settings panel now resolves `settings.title`, `settings.creditExpiration`,
|
||
`settings.enabled`, `settings.disabled`, `settings.expirationPeriod`, `settings.daysUnit`,
|
||
`settings.notificationDays`, and `settings.none`, leaving no page-local settings labels in
|
||
raw English.
|
||
- **(2026-04-17, F013)** Wired `packages/billing/src/components/credits/AddCreditButton.tsx`
|
||
to `useTranslation('msp/credits')`. The trigger label, dialog title, placeholder paragraph,
|
||
cancel action, and submit action all now resolve from `actions.*` / `management.addCreditPlaceholder`.
|
||
- **(2026-04-17, F014)** Wired `packages/billing/src/components/credits/BackButton.tsx` to
|
||
`useTranslation('msp/credits')` and moved the visible label to `actions.backToCredits`.
|
||
- **(2026-04-17, F015)** Started the `CreditManagement.tsx` translation pass by wiring
|
||
`useTranslation('msp/credits')` for the dashboard shell. Translated the page title, chart
|
||
card titles/descriptions, stat tiles, legend labels, and placeholder month labels; moved the
|
||
expiration bucket labels into a `generateExpirationChartData(..., t)` helper and derived the
|
||
pie-chart labels in a locale-aware effect so pseudo/de locale changes update chart copy too.
|
||
- **(2026-04-17, F016)** Finished the `CreditManagement` table/dialog shell wiring. Refactored
|
||
the credits table columns into `createColumns(t)` so column titles, `N/A`/`Never` fallbacks,
|
||
status badges, row action buttons, the “Recent Credits” section copy, tab labels, “View All
|
||
Credits” CTA, and the local add-credit dialog all now resolve from `msp/credits`.
|
||
- **(2026-04-17, F017)** Threaded `t` through `renderCreditContext()` in `CreditManagement.tsx`
|
||
so the lineage/status explanation blocks now resolve `context.*` keys instead of rendering
|
||
raw English helper text.
|
||
- **(2026-04-17, F018)** Began `CreditReconciliation.tsx` wiring by translating the dashboard
|
||
heading, client selector placeholder, run button state, filter labels/options, reset action,
|
||
and the validation toast message with `balanceCount` / `trackingCount` interpolation.
|
||
- **(2026-04-17, F019)** Completed the reporting surface in `CreditReconciliation.tsx`. The
|
||
stat tiles, chart titles/descriptions, chart legend labels, month placeholders, table card
|
||
copy, report-table columns, status badges, row action buttons, and tab labels with counts now
|
||
all resolve from `msp/credits`.
|
||
- **(2026-04-17, F020)** Wired `CreditApplicationUI.tsx` to `useTranslation('msp/credits')`.
|
||
All card copy, table columns, selection buttons, labels, helper text, empty states, and the
|
||
generic load/apply validation errors now resolve from the credits namespace instead of raw
|
||
English strings.
|
||
- **(2026-04-17, F021)** Wired `CreditExpirationInfo.tsx` to `useTranslation('msp/credits')`.
|
||
The card title, applied-amount description, field labels, “Never” fallback, empty/error
|
||
states, and ordering note now all resolve through `expiration.*`, with the applied amount
|
||
kept as an interpolation value rather than a translated currency string.
|
||
- **(2026-04-17, F022)** Wired `CreditExpirationModificationDialog.tsx` to
|
||
`useTranslation('msp/credits')`. The dialog title/description, field labels, switch copy,
|
||
input label, cancel/save button states, past-date validation message, and generic update
|
||
failure all now resolve from `expirationDialog.*` / shared `actions.*`, and raw thrown errors
|
||
are no longer surfaced to the user.
|
||
|
||
## Key Paths / Runbooks
|
||
|
||
- English source namespace: `server/public/locales/en/msp/credits.json`
|
||
- Pseudo-locale generator: `node scripts/generate-pseudo-locales.cjs`
|
||
- Translation validator: `node scripts/validate-translations.cjs`
|
||
- Route namespace config: `packages/core/src/lib/i18n/config.ts`
|
||
- Credits page client wrapper: `packages/billing/src/components/credits/CreditsPageClient.tsx`
|
||
- Credits page server loader: `packages/billing/src/components/credits/CreditsPage.tsx`
|
||
- Credits namespace/route tests: `packages/billing/tests/creditsNamespaceAndRoute.i18n.test.ts`
|
||
- Plan artifacts:
|
||
`ee/docs/plans/2026-04-09-msp-i18n-credits/{PRD.md,SCRATCHPAD.md,features.json,tests.json}`
|
||
|
||
## Test Log
|
||
|
||
- **(2026-04-17, T001)** Added `packages/billing/tests/creditsNamespaceAndRoute.i18n.test.ts`
|
||
with an executable validator contract that reruns pseudo-locale generation plus
|
||
`validate-translations.cjs` and asserts the combined pipeline stays green (`PASSED`,
|
||
`Errors: 0`, `Warnings: 0`).
|
||
- **(2026-04-17, T002)** Extended the same namespace/route contract file with a strict top-level
|
||
shape assertion for `server/public/locales/en/msp/credits.json`. This guards against accidental
|
||
group drift when future i18n edits touch the credits namespace.
|
||
- **(2026-04-17, T003/T004)** Added route invariants to
|
||
`packages/billing/tests/creditsNamespaceAndRoute.i18n.test.ts` that assert both the literal
|
||
`ROUTE_NAMESPACES['/msp/billing/credits']` value and the runtime result of
|
||
`getNamespacesForRoute('/msp/billing/credits')` / nested credit paths. This protects the
|
||
longest-prefix behavior called out in the PRD.
|
||
- **(2026-04-17, T005-T008)** Added `packages/billing/tests/CreditsPage.i18n.test.ts` as the
|
||
wrapper/server-page contract. The file checks that `CreditsPage.tsx` delegates to
|
||
`CreditsPageClient`, that the client wrapper wires translated columns/status/tabs/settings
|
||
summary keys, and that representative credits-page keys resolve to pseudo-locale fill in
|
||
`xx/msp/credits.json`.
|
||
- **(2026-04-17, T009-T012)** Added `packages/billing/tests/CreditControls.i18n.test.ts` for
|
||
the small page-shell controls. It asserts `AddCreditButton` and `BackButton` both import
|
||
`useTranslation('msp/credits')` and checks the representative `xx` pseudo-locale keys those
|
||
controls depend on.
|
||
- **(2026-04-17, T013-T016)** Added `packages/billing/tests/CreditManagement.i18n.test.ts` to
|
||
cover the translated dashboard shell, chart legend labels/month placeholders, recurring-
|
||
lineage context helper copy, and representative pseudo-locale coverage for the management tab.
|
||
- **(2026-04-17, T017-T020)** Added `packages/billing/tests/CreditReconciliation.i18n.test.ts`
|
||
covering the translated dashboard/filter shell, report charts/table/status badges, the
|
||
interpolated validation toast copy, and representative `xx` pseudo-locale backing for the
|
||
reconciliation surface.
|
||
- **(2026-04-17, T021-T026)** Added `packages/billing/tests/CreditApplicationExpiration.i18n.test.ts`
|
||
to cover the remaining credit application and expiration components: application card/table
|
||
copy, error/empty/help states, applied-credit interpolation, and the expiration dialog’s
|
||
label/validation/error wiring.
|
||
- **(2026-04-17, T027-T031)** Added `packages/billing/tests/CreditsLocaleSmoke.i18n.test.ts`
|
||
for the final locale smoke layer: Italian accent/contracted-form checks, interpolation-token
|
||
parity across all translated locales, representative English route-shell coverage, German
|
||
non-English coverage for the management/reconciliation dashboards, and representative `xx`
|
||
pseudo-fill checks across page/management/reconciliation/application/expiration flows.
|