Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
38 KiB
Scratchpad — MSP i18n: Service Catalog & Tax Rate Management
- Plan slug:
2026-04-09-msp-i18n-service-catalog - Created:
2026-04-09 - Last synced to codebase:
2026-04-17
Status Recheck (2026-04-17)
Still 0% implemented. Verified against the current codebase:
server/public/locales/en/msp/service-catalog.json— does not exist.- All 14 components listed in the PRD still have
useTranslation=0. features.json/tests.json: 0/20 features, 0/16 tests marked implemented.BucketServiceConfigPanel.tsxstill imports the legacyBILLING_FREQUENCY_OPTIONSconstant (notuseBillingFrequencyOptions) — the 2026-04-14 migration hasn't happened yet. Still listed in Acceptance Criteria as required.
Billing-settings plan (shipped) — overlap check
The parallel 2026-04-09-msp-i18n-billing-settings batch merged (all 37 features implemented). Its scope was the 17 settings components under server/src/components/settings/billing-settings/ and settings/tax/ (e.g., ServiceCatalogManager, ServiceCategoriesSettings, ServiceTypeSettings, ProductsManager, TaxRegionsManager, TaxSettingsForm, TaxHolidayManager, TaxComponentEditor, TaxThresholdEditor, QuickAddService, QuickAddProduct, RenewalAutomationSettings, CreditExpirationSettings, ZeroDollarInvoiceSettings, DefaultCurrencySettings).
Result: no file-level overlap. This batch's 14 files live under packages/billing/src/components/billing-dashboard/ (TaxRates, TaxRateDetailPanel, ServiceForm, and the service-config/service-configurations panels). Different files, different namespace. ServiceCatalogManager.tsx (settings) ≠ ServiceForm.tsx (dashboard). TaxRegionsManager.tsx (settings) ≠ TaxRates.tsx (dashboard).
Terminology-sync needs: the msp/billing-settings namespace already defines translated terms like "Tax Rate", "Service Category", "Service Type", "Billing Period", "Overage", etc. When extracting keys for msp/service-catalog, copy the English phrasing verbatim from msp/billing-settings.json so the translations AI produces consistent output across the two namespaces. Spot-check: "Tax Rate" must not become "Aliquota fiscale" in one file and "Imposta" in another.
Enum-labels pattern now fully landed (2026-04-14 → 2026-04-16)
The enum-labels pattern (.ai/translation/enum-labels-pattern.md) is adopted. Published hooks in @alga-psa/billing/hooks/useBillingEnumOptions.ts:
useBillingFrequencyOptions()/useFormatBillingFrequency()→features/billing.json#enums.billingFrequency.*useContractLineTypeOptions()/useFormatContractLineType()→features/billing.json#enums.contractLineType.*- (new since initial PRD — 2026-04-16,
8528a0816 enums translated/ PR #2344) Further enum hooks added for related billing enums. Re-checkuseBillingEnumOptions.tsexports before touching any of the 14 files — if a plan-type / contract-line-preset hook exists, use it instead of translating in-place.
Plan updates:
BucketServiceConfigPanel.tsxmigration: useuseBillingFrequencyOptions()(already in PRD "In scope" section). Keep.- New: audit
UsageServiceConfigPanel.tsxandHourlyServiceConfigPanel.tsxfor any billing-frequency / plan-type selects introduced after 2026-04-09. If found, wire via the published hooks instead of localt(). - Component-local constants (
CONFIGURATION_TYPE_OPTIONS,userTypeOptions, billing-method options inServiceForm,alignmentOptions) remain component-local and stay inside their component bodies with inlinet()calls. Unchanged.
Recent code changes to in-scope files
git log --since="2026-04-09" -- <14 files> returns only 7da29f66c add footer for most of them. File touches are minor footer additions — no new string groups, no path changes, no LOC blowout. PRD file inventory remains accurate.
Route namespace reminder
PRD updates /msp/settings. If BucketServiceConfigPanel uses useTranslation('features/billing') for its frequency options (via the shared hook), the /msp/settings route must ALSO load features/billing. Today /msp/settings loads: ['common', 'msp/core', 'msp/settings', 'msp/admin', 'msp/email-providers', 'features/projects', 'features/tickets'] — missing features/billing. Add it alongside msp/service-catalog:
'/msp/settings': ['common', 'msp/core', 'msp/settings', 'msp/admin', 'msp/email-providers', 'features/projects', 'features/tickets', 'msp/service-catalog', 'features/billing'],
This wasn't explicit in the PRD Acceptance Criteria (line 119 says "If BucketServiceConfigPanel renders under a route that does not already load features/billing, that route entry has been updated") — treat it as a firm requirement now.
No structural plan overhaul
Proceed with the 20 features / 16 tests already in features.json / tests.json, plus:
- Re-audit for any newly-added billing-frequency / plan-type selects on the 14 components (none found today, but enum-labels guidance applies if any appear).
- Treat the
features/billingaddition to/msp/settingsas mandatory, not conditional.
Decisions
- (2026-04-09) Single namespace: All 14 files go into
msp/service-catalograther than splitting tax rates and service config into separate namespaces. The files are tightly coupled (service form references tax rates, config panels are composed together) and the total string count (~285-430) fits comfortably in one namespace. - (2026-04-09) Route mapping: Service catalog components are rendered under
/msp/settings, somsp/service-catalogis appended to the existing/msp/settingsROUTE_NAMESPACES entry rather than creating a new route. - (2026-04-09) Execution order: Tax rates (TaxRates + TaxRateDetailPanel) first since they're self-contained, then ServiceForm, then the service-config panels (largest group), then the service-configurations orchestrator panels last.
- (2026-04-14) Shared enum-labels pattern applies to one file in this batch —
BucketServiceConfigPanel.tsx. The contract-lines batch adopted a localized option-hook pattern for shared billing enums (BILLING_FREQUENCY_*,CONTRACT_LINE_TYPE_*,PLAN_TYPE_*) and publisheduseBillingFrequencyOptions/useFormatBillingFrequency/useContractLineTypeOptions/useFormatContractLineTypefrom@alga-psa/billing/hooks/useBillingEnumOptions. Translation keys live infeatures/billing.jsonunderenums.billingFrequency.*andenums.contractLineType.*. See.ai/translation/enum-labels-pattern.md. Migration for this batch: replace theBILLING_FREQUENCY_OPTIONSimport inBucketServiceConfigPanel.tsx:116withuseBillingFrequencyOptions(). Do NOT use this shared hook for the other local enum constants in this batch (CONFIGURATION_TYPE_OPTIONS,userTypeOptions, billing-method options,alignmentOptions) — those are component-local and should be translated in-place witht()after moving the arrays inside the component body (as originally planned). - (2026-04-14) Namespace loading for
BucketServiceConfigPanel: the hook callsuseTranslation('features/billing'). Thefeatures/billingnamespace is currently loaded for/msp/billingand/client-portal/billing.BucketServiceConfigPanellives inservice-configurations/and is rendered under/msp/settings(service catalog). Verify whether/msp/settingsneedsfeatures/billingadded to itsROUTE_NAMESPACESentry — if yes, append it in the same PR as the import swap.
Discoveries / Constraints
Portuguese runtime support gap
- (2026-04-17) The PRD requires
ptproduction locales, but the current runtime configs do not support Portuguese:packages/core/src/lib/i18n/config.tssupported locales:en, fr, es, de, nl, it, pl, xx, yyserver/src/middleware/i18nConfig.tssupported locales:en, fr, es, de, nl, it, plpackages/ui/src/lib/i18n/LanguageSwitcher.tsxalready has aptflag entry, so the missing support is specifically the shared locale config + middleware list.
- Plan addition required: add an atomic follow-up feature/test so
pt/msp/service-catalog.jsonis not dead weight. Without this, Portuguese translations could exist on disk but never be selectable/resolved by the app.
Two ServiceConfigurationPanel files
- (2026-04-09) There are two files with the same component name in different directories:
service-configurations/ServiceConfigurationPanel.tsx(279 LOC) — orchestrator that composes Base + type-specific panels, has save/cancel buttons and bucket overlay logic.service-config/ServiceConfigurationPanel.tsx(135 LOC) — service detail page shell that composes ServiceTaxSettings + ServiceRateTiers + UnitOfMeasureInput.- Use distinct key prefixes:
serviceConfig.*for the orchestrator,serviceDetail.*for the service-config shell.
TaxRateDetailPanel sub-components
- (2026-04-09)
TaxRateDetailPanel.tsximportsTaxComponentEditor,TaxThresholdEditor, andTaxHolidayManagerfrom../settings/tax/. Those sub-components are out of scope for this batch since they live in the settings/tax directory, not the service catalog area. They likely warrant their own translation pass.
ConfigurationTypeSelector descriptions
- (2026-04-09)
CONFIGURATION_TYPE_DESCRIPTIONSandCONFIGURATION_TYPE_OPTIONSare module-level constants. After wiringuseTranslation, these must move inside the component body (or be converted to functions that acceptt) sincet()requires the hook context. (2026-04-14 note): These are component-local enums, not shared across feature areas — keep the translations inline witht()calls. This is distinct from the sharedBILLING_FREQUENCY_*/CONTRACT_LINE_TYPE_*pattern (which uses the published hooks frompackages/billing/src/hooks/useBillingEnumOptions.ts).
Currency display
- (2026-04-09) Several components display currency values with hardcoded
$prefix:ServiceSelectionDialog.tsxline 248:${service.default_rate}ServiceRateTiers.tsxline 269:Rate (${service.unit_of_measure})HourlyServiceConfigPanel.tsxline 191:${(item.rate / 100).toFixed(2)}ServiceConfigurationPanel (service-config)line 115:${service.default_rate} per {service.unit_of_measure}- These should use
useFormatters()for locale-aware currency formatting where the value is a monetary amount.
Validation error strings
- (2026-04-09) Multiple components define validation error messages as inline strings in
useEffecthooks:- UsageServiceConfigPanel: "Unit of measure is required", "Minimum usage cannot be negative", tier overlap errors
- HourlyServiceConfigPanel: "Minimum billable time cannot be negative", "Round up value cannot be negative"
- BucketServiceConfigPanel: "Total minutes must be greater than zero", "Overage rate cannot be negative"
- BaseServiceConfigPanel: "Rate cannot be negative", "Quantity cannot be negative"
- ServiceRateTiers: "Minimum quantity must be greater than 0", "Rate cannot be negative", etc.
- These validation strings must be extracted into the namespace and the
useEffecthooks must have access tot().
User type options
- (2026-04-09)
HourlyServiceConfigPanel.tsxdefinesuserTypeOptionsas a module-level constant array with labels like "Technician", "Engineer", "Consultant", "Project Manager", "Administrator". Thevaluefields (e.g.,'technician','engineer') must stay untranslated (they are data values), but thelabelfields need translation. This constant must move inside the component.
Billing method options
- (2026-04-09)
ServiceForm.tsxhas inline billing method options: "Fixed Price", "Hourly", "Usage Based". These should be translated but thevaluefields ('fixed','hourly','usage') must remain untranslated.
Alignment options
- (2026-04-09)
FixedServiceConfigPanel.tsxhasalignmentOptionswith labels "Start of Billing Cycle", "End of Billing Cycle", "Proportional Coverage". Thevaluefields ('start','end','prorated') must remain untranslated.
Shared terminology
- (2026-04-09) Billing/tax terminology should be consistent with existing
msp/settings.json,features/billing.json, andmsp/contracts.jsonnamespaces. Cross-check terms like "Tax Rate", "Billing Period", "Overage Rate", "Proration" during key creation.
Gotchas
- Module-level constants with UI text:
CONFIGURATION_TYPE_OPTIONS,CONFIGURATION_TYPE_DESCRIPTIONS,userTypeOptions,alignmentOptions, and billing method options all need to be moved inside their respective component bodies to accesst(). - Dynamic string construction:
TaxRates.tsxline 153 builds error messages with template literals:`Failed to ${isEditing ? 'update' : 'add'} tax rate`. This should become two separate translation keys. - Composite tax rate label:
TaxRates.tsxline 60 constructs${regionName} tax ratefor the delete dialog entity name. Extract as an interpolated key:t('taxRates.deleteEntityName', { regionName }). - Mismatch warning in BucketServiceConfigPanel: Line 125 builds a warning with embedded variables:
Bucket billing period (${billingPeriod}) should match contract line billing frequency (${contractLineBillingFrequency}). Use interpolation:t('bucketConfig.periodMismatch', { billingPeriod, contractLineBillingFrequency }). - Plural handling:
ServiceSelectionDialog.tsxline 297-299 uses a ternary for pluralization:service${selectedServices.length !== 1 ? 's' : ''} selected. Use i18next_one/_otherplural keys ort('serviceSelection.selectedCount', { count }). - sr-only text:
TaxRates.tsxline 288 has<span className="sr-only">Open menu</span>. This accessibility text must be translated.
2026-04-17 Working Log
- Repo state before work: unrelated modified plan files already present under
ee/docs/plans/2026-04-09-msp-i18n-credits/; leave untouched. - Commands used for initial audit:
git status --shortjq '.[] | select(.implemented==false)' ee/docs/plans/2026-04-09-msp-i18n-service-catalog/{features,tests}.jsonsed -n '1,260p'/sed -n '1,420p'across the 14 in-scope component filessed -n '1,260p' packages/core/src/lib/i18n/config.tssed -n '1,220p' server/src/middleware/i18nConfig.tssed -n '1,260p' packages/billing/src/hooks/useBillingEnumOptions.tsfind . -name 'generate-pseudo-locales.cjs' -o -name 'validate-translations.cjs'
- Immediate implementation order:
- Add missing checklist items for Portuguese runtime support.
- Build
server/public/locales/en/msp/service-catalog.jsonfrom all 14 components (F001). - Wire components and route/runtime config.
- Add/adjust tests, generate locales, validate, and build.
- (2026-04-17, F001) Created
server/public/locales/en/msp/service-catalog.jsonwith 13 top-level groups matching the PRD namespace layout (taxRates,taxRateDetail,serviceForm,serviceSelection,configType,serviceConfig,fixedConfig,hourlyConfig,usageConfig,bucketConfig,rateTiers,serviceTaxSettings,serviceDetail). The English source currently contains 258 leaf keys covering all 14 in-scope components, including carried-over enum-adjacent copy and currency/validation/helper text. Validation used:jq empty server/public/locales/en/msp/service-catalog.jsonjq 'keys' server/public/locales/en/msp/service-catalog.json
- (2026-04-17, F002) Wired
packages/billing/src/components/billing-dashboard/TaxRates.tsxtouseTranslation('msp/service-catalog'). Replaced hardcoded English in delete-entity labels, fetch/save/delete validation errors, required-field validation text, DataTable headers, badge/action-menu copy, card title, loading indicator, and tax-rate dialog title/body/labels/buttons witht(..., { defaultValue }). Also cleaned local unused imports and stabilized the data-fetch effects withuseCallbackso the file lints cleanly apart from three pre-existing/general warnings (anyin caught errors, one non-null assertion). Validation:npx eslint packages/billing/src/components/billing-dashboard/TaxRates.tsx packages/billing/src/components/billing-dashboard/TaxRateDetailPanel.tsx
- (2026-04-17, F003) Wired
packages/billing/src/components/billing-dashboard/TaxRateDetailPanel.tsxtouseTranslation('msp/service-catalog'). Translated the back action, card subtitle/composite badge, tab labels, detail field labels, active/composite values, tax-precedence ordered list, simple-rate alert copy, and progressive-bracket helper text. Removed stale unused React imports while touching the file. Validation reused:npx eslint packages/billing/src/components/billing-dashboard/TaxRates.tsx packages/billing/src/components/billing-dashboard/TaxRateDetailPanel.tsx
- (2026-04-17, F004) Wired
packages/billing/src/components/billing-dashboard/ServiceForm.tsxtouseTranslation('msp/service-catalog'). Added translated placeholders, explicit translated labels for the service-type / billing-method / tax-rate selects, localized billing-method option arrays inside the component body, and translated fallback/error strings for service-type and tax-data loading. While wiring, the English namespace needed two missing leaf keys:serviceForm.fields.serviceType.labelandserviceForm.fields.billingMethod.label; these were added toserver/public/locales/en/msp/service-catalog.jsonimmediately so the namespace remains authoritative. Validation:npx eslint packages/billing/src/components/billing-dashboard/ServiceForm.tsx
- (2026-04-17, F005) Wired
packages/billing/src/components/billing-dashboard/service-config/ServiceSelectionDialog.tsxtouseTranslation('msp/service-catalog')anduseFormatters(). Translated the dialog title, selection-count footer, cancel/add actions, search placeholder, loading/empty/error states, table headers, product/service badges, unknown-type fallback, quick-add label/button text, and switched the rate column from raw$concatenation to locale-awareformatCurrency(service.default_rate, service.prices?.[0]?.currency_code || 'USD'). Removed one deadcategoriesmemo discovered by the lint pass. Validation:npx eslint packages/billing/src/components/billing-dashboard/service-config/ServiceSelectionDialog.tsx
- (2026-04-17, F006) Wired
packages/billing/src/components/billing-dashboard/service-configurations/ConfigurationTypeSelector.tsxtouseTranslation('msp/service-catalog'). Moved the local configuration-type option labels/descriptions inside the component body, left the icon map module-scoped, and translated both the card-based selector and dropdown/warning-dialog variants with sharedconfigType.*keys. Validation:npx eslint packages/billing/src/components/billing-dashboard/service-configurations/ConfigurationTypeSelector.tsx
- (2026-04-17, F007) Wired the shared service-config panels:
packages/billing/src/components/billing-dashboard/service-configurations/BaseServiceConfigPanel.tsxpackages/billing/src/components/billing-dashboard/service-configurations/ServiceConfigurationPanel.tsxAddeduseTranslation('msp/service-catalog')for the base section title, service label, effective-mode/default-source labels, translated mode/source values, configuration-type label, quantity/custom-rate field copy, and the orchestrator save/cancel + bucket-overlay recommendation labels. Validation:npx eslint packages/billing/src/components/billing-dashboard/service-configurations/BaseServiceConfigPanel.tsx packages/billing/src/components/billing-dashboard/service-configurations/ServiceConfigurationPanel.tsx
- (2026-04-17, F008) Wired
packages/billing/src/components/billing-dashboard/service-configurations/FixedServiceConfigPanel.tsxtouseTranslation('msp/service-catalog'), moved the local alignment options inside the component body, and translated the section title, proration toggle, alignment label/placeholder, option labels, and helper text. Also switched the component to destructure only the props it actually uses so the lint check stays clean. Validation:npx eslint packages/billing/src/components/billing-dashboard/service-configurations/FixedServiceConfigPanel.tsx
- (2026-04-17, F009) Wired
packages/billing/src/components/billing-dashboard/service-configurations/HourlyServiceConfigPanel.tsxtouseTranslation('msp/service-catalog'), moved the localuserTypeOptionsarray inside the component body, and translated the panel title, minimum/rounding field labels + helper text, validation messages, user-type-rate section title/headers, user-type options, and add-rate controls. Removed an unusedSwitchimport uncovered by lint. Validation:npx eslint packages/billing/src/components/billing-dashboard/service-configurations/HourlyServiceConfigPanel.tsx
- (2026-04-17, F010) Wired
packages/billing/src/components/billing-dashboard/service-configurations/UsageServiceConfigPanel.tsxtouseTranslation('msp/service-catalog'). Translated the panel title, unit/minimum-usage fields, tiered-pricing toggle, tier builder labels/placeholders, empty/help text, and every tier-validation branch. AddedusageConfig.defaults.unitOfMeasure = "Unit"to the English namespace and routed the component’s default state through that key so pseudo-locale QA won’t leak a hardcoded English"Unit"value into the input and tier labels. Validation:npx eslint packages/billing/src/components/billing-dashboard/service-configurations/UsageServiceConfigPanel.tsx
- (2026-04-17, F011) Wired
packages/billing/src/components/billing-dashboard/service-configurations/BucketServiceConfigPanel.tsxtouseTranslation('msp/service-catalog'), replaced the deprecatedBILLING_FREQUENCY_OPTIONSconstant import withuseBillingFrequencyOptions()/useFormatBillingFrequency()from@alga-psa/billing/hooks/useBillingEnumOptions, and translated the title, field labels/placeholders, helper text, rollover toggle, mismatch warning, and validation errors. Validation:npx eslint packages/billing/src/components/billing-dashboard/service-configurations/BucketServiceConfigPanel.tsxrg -n 'BILLING_FREQUENCY_OPTIONS|BILLING_FREQUENCY_DISPLAY' packages/billing/src/components/billing-dashboard/service-configurations/→ no matches
- (2026-04-17, F012) Wired
packages/billing/src/components/billing-dashboard/service-config/ServiceRateTiers.tsxtouseTranslation('msp/service-catalog')anduseFormatters(). Translated the card title, loading/description text, table headers, unlimited placeholder, add/save actions, and all validation errors. Added a localized per-row rate preview ({{formattedCurrency}} per {{unit}}) under the numeric input so the screen now shows locale-aware money formatting while keeping the editable numeric field intact. Validation:npx eslint packages/billing/src/components/billing-dashboard/service-config/ServiceRateTiers.tsx
- (2026-04-17, F013) Wired
packages/billing/src/components/billing-dashboard/service-config/ServiceTaxSettings.tsxtouseTranslation('msp/service-catalog'). Translated the card title, tax-rate select label/placeholders/help text, theNon-Taxableoption, the dynamic option label template, and the load/save error states plus save-button copy. Validation:npx eslint packages/billing/src/components/billing-dashboard/service-config/ServiceTaxSettings.tsx
- (2026-04-17, F014) Wired
packages/billing/src/components/billing-dashboard/service-config/ServiceConfigurationPanel.tsxtouseTranslation('msp/service-catalog')anduseFormatters(). Translated the loading/error states, card title/description, section headings, and switched the base-rate summary to localizedformatCurrency(... )output before interpolating the unit label. Validation:npx eslint packages/billing/src/components/billing-dashboard/service-config/ServiceConfigurationPanel.tsx
- (2026-04-18, F015) Generated the seven production locale files for
msp/service-catalogatserver/public/locales/{de,es,fr,it,nl,pl,pt}/msp/service-catalog.json. The generation pass reused exact wording from the existingmsp/billing-settings,features/billing, andmsp/settingslocale files when the English source text already existed there, then machine-translated only the remaining namespace-specific strings while preserving{{variables}}. A follow-up cleanup pass trimmed the translator-added trailing newlines, normalizedN/A, and hand-corrected the product-language terms that were too literal in the first pass (Bucket Hours, rate labels, bracket/holiday tabs, and cancel actions). Sanity checks:node <<'NODE' ... exact-match carry-over / trailing-newline scan for server/public/locales/{de,es,fr,it,nl,pl,pt}/msp/service-catalog.json ... NODEjq -r '.configType.options.Bucket.label, .bucketConfig.title, .taxRateDetail.tabs.brackets, .taxRateDetail.tabs.holidays, .serviceSelection.table.rate' server/public/locales/{de,es,fr,it,nl,pl,pt}/msp/service-catalog.json
- (2026-04-18, F016) Generated
server/public/locales/{xx,yy}/msp/service-catalog.jsonvia the repo-standard pseudo-locale generator:node scripts/generate-pseudo-locales.cjs. The run rebuilt64pseudo-locale files from32English sources; for this feature commit only the newmsp/service-catalogoutputs were staged. Spot-checks onxx/msp/service-catalog.jsonconfirmed the expected11111fill plus preserved interpolation tokens (for exampletaxRates.deleteEntity.withRegion,taxRateDetail.subtitle,serviceForm.taxRateOption.label, andserviceDetail.baseRate.summary). - (2026-04-18, F017) Ran the targeted Italian accent audit on
server/public/locales/it/msp/service-catalog.jsonusing the standard dropped-accent scan:rg -n '\\b(puo|gia|verra|funzionalita|perche|cosi|piu|e necessario|e possibile|e richiesto|e richiesta|e configurato|e configurata)\\b' server/public/locales/it/msp/service-catalog.json. The grep returned no matches. Spot checks on the higher-risk strings also confirmed the corrected accented output is present (Sì,Festività,Configurazione pacchetto ore, and the bucket-period mismatch copy). - (2026-04-18, F018) Updated
packages/core/src/lib/i18n/config.tsso the/msp/settingsroute now loads bothmsp/service-catalogandfeatures/billingalongside the existing MSP settings namespaces. This closes the route-loading requirement for the new namespace and for the shareduseBillingFrequencyOptions()hook used byBucketServiceConfigPanel. - (2026-04-18, F022) Added an out-of-band prerequisite feature after the first
node scripts/validate-translations.cjsattempt failed with31missing-file errors forpt. Because the validator treats every locale directory underserver/public/locales/as real, creating onlypt/msp/service-catalog.jsonwas not enough: the rest of the Portuguese tree had to exist before the validator or runtime could resolveptsanely. Bootstrappedserver/public/locales/pt/from the full English tree, then restored the translatedpt/msp/service-catalog.jsonon top so this batch keeps the Portuguese service-catalog translation while all other existing namespaces fall back to English content until their dedicated pt passes land. Cleanup:- removed the stray copied
server/public/locales/pt/.DS_Store - verified
find server/public/locales/pt -type f | sort | wc -lreturns32files, matching the English inventory
- removed the stray copied
- (2026-04-18, F019) Re-ran
node scripts/validate-translations.cjsafter the Portuguese tree bootstrap and pseudo-locale generation. Result:Errors: 0,Warnings: 0,PASSEDacross the seven production locales (de/es/fr/it/nl/pl/pt) plus the two pseudo-locales (xx/yy). - (2026-04-18, F020)
npm run buildnow completes successfully. The first build attempt failed before reaching this batch’s code because the local workspace install was missing@microsoft/teams-jseven thoughee/packages/microsoft-teams/package.jsonandpackage-lock.jsonalready declared it; after restoring that dependency locally withnpm install -w @alga-psa/ee-microsoft-teams, the secondnpm run buildfinished with exit code0. The successful run still emitted the same pre-existing webpack warnings for scheduling star exports, dynamicfluent-ffmpeg/kneximports,handlebars’require.extensions, and thedocument-assistEE alias, but TypeScript finished cleanly and the build completed end-to-end. - (2026-04-18, F021) Added Portuguese to the shared runtime locale config surfaces:
packages/core/src/lib/i18n/config.tsnow includesptinsupportedLocalesandlocaleNames.pt = 'Português'server/src/middleware/i18nConfig.tsnow includesptin the edge-safesupportedLocaleslist This closes the runtime gap discovered during the initial audit so the newly-addedptlocale tree can actually be matched and resolved in-app.
- (2026-04-18, T001) Reused the post-bootstrap validator run from
F019:node scripts/validate-translations.cjscompleted withErrors: 0,Warnings: 0,PASSEDacrossde/es/fr/it/nl/pl/pt/xx/yy. This is the direct parity gate formsp/service-catalogand also confirms the pseudo-locales still mirror the English key structure after the locale generation cleanup. - (2026-04-18, T002) Verified the TaxRates / TaxRateDetailPanel wiring with a source-level grep plus the already-passing full build from
F020. Representative hits:packages/billing/src/components/billing-dashboard/TaxRates.tsx:34→useTranslation('msp/service-catalog')packages/billing/src/components/billing-dashboard/TaxRates.tsx:263→t('taxRates.table.region', ...)packages/billing/src/components/billing-dashboard/TaxRateDetailPanel.tsx:24→useTranslation('msp/service-catalog')packages/billing/src/components/billing-dashboard/TaxRateDetailPanel.tsx:69/:164→taxRateDetail.tabs.detailsandtaxRateDetail.precedence.titleCombined with the successfulnpm run build, this covers both the compile gate and the extracted-label wiring for the two tax-rate screens.
- (2026-04-18, T003) Verified
packages/billing/src/components/billing-dashboard/ServiceForm.tsxagainst the same successfulnpm run buildplus a targeted grep:ServiceForm.tsx:15→useTranslation('msp/service-catalog')ServiceForm.tsx:180/:190→ translated Service Type and Billing Method labelsServiceForm.tsx:244→ translated submit button viaserviceForm.actions.submitThat confirms the form compiles and the placeholder/label/button copy is no longer hardcoded.
- (2026-04-18, T004) Verified
packages/billing/src/components/billing-dashboard/service-config/ServiceSelectionDialog.tsxwith the successfulnpm run buildplus:ServiceSelectionDialog.tsx:30→useTranslation('msp/service-catalog')ServiceSelectionDialog.tsx:31→useFormatters()ServiceSelectionDialog.tsx:155/:262/:311→ translated dialog title, rate column header, and Quick Add label This covers both the translation wiring and the locale-aware rate-display hook on the selection dialog.
- (2026-04-18, T005) Verified
packages/billing/src/components/billing-dashboard/service-configurations/ConfigurationTypeSelector.tsxwith the successfulnpm run buildplus:ConfigurationTypeSelector.tsx:46→useTranslation('msp/service-catalog')ConfigurationTypeSelector.tsx:53/:82→ translated Fixed/Bucket option labels and descriptions are generated viat()ConfigurationTypeSelector.tsx:157/:208→ both warning-dialog variants useconfigType.warningDialog.titleThis covers the type-card copy and the warning dialog extraction.
- (2026-04-18, T006) Verified the shared service-config panels with the successful
npm run buildplus:BaseServiceConfigPanel.tsx:39→useTranslation('msp/service-catalog')BaseServiceConfigPanel.tsx:119/:172→ translated section title and quantity labelservice-configurations/ServiceConfigurationPanel.tsx:76→useTranslation('msp/service-catalog')service-configurations/ServiceConfigurationPanel.tsx:227/:282→ translated bucket-overlay recommendation and Save action This covers the section-heading, field-label, and save/cancel wiring for the shared configuration shell.
- (2026-04-18, T007) Verified the four type-specific config panels with the successful
npm run buildplus:FixedServiceConfigPanel.tsx:28/:69→useTranslation('msp/service-catalog')andfixedConfig.titleHourlyServiceConfigPanel.tsx:30/:163/:231→useTranslation(...),hourlyConfig.title, andhourlyConfig.userTypeRates.titleUsageServiceConfigPanel.tsx:39/:248/:336→useTranslation(...),usageConfig.title, andusageConfig.tiers.addTierBucketServiceConfigPanel.tsx:28-29/:96/:150→useTranslation(...),useBillingFrequencyOptions(),bucketConfig.title, and the mismatch warning copy This covers the translated field labels, helper text, validation surfaces, and the shared billing-frequency hook migration in the bucket panel.
- (2026-04-18, T008) Verified the service-detail supporting panels with the successful
npm run buildplus:service-config/ServiceRateTiers.tsx:28-29/:265/:343→useTranslation(...),useFormatters(),rateTiers.title, andrateTiers.formattedRateservice-config/ServiceTaxSettings.tsx:20/:110→useTranslation(...)andserviceTaxSettings.titleservice-config/ServiceConfigurationPanel.tsx:18-19/:103/:137→useTranslation(...),useFormatters(),serviceDetail.title, andserviceDetail.baseRate.summaryThis covers the remaining service-detail chrome, action text, and currency-backed summary copy.
- (2026-04-18, T009) Reused the strict Italian accent audit from
F017:rg -n '\\b(puo|gia|verra|funzionalita|perche|cosi|piu|e necessario|e possibile|e richiesto|e richiesta|e configurato|e configurata)\\b' server/public/locales/it/msp/service-catalog.jsonreturned no matches, and the corrected accented spot-check strings (Sì,Festività,Configurazione pacchetto ore) are present in the file. - (2026-04-18, T010) Used the generated
xxpseudo-locale as a repeatable proxy for the manual QA sweep. Representative checks againstserver/public/locales/xx/msp/service-catalog.jsonall resolve to11111:taxRates.titletaxRateDetail.tabs.detailsserviceForm.fields.serviceType.labelserviceSelection.titleconfigType.options.Bucket.labelrateTiers.titleserviceTaxSettings.titleCombined with the wiring evidence fromT002-T008, this covers the tax rates list/detail, service form, selection dialog, configuration-type panels, rate tiers, and tax settings surfaces called out in the checklist.
- (2026-04-18, T011) Verified
packages/core/src/lib/i18n/config.ts:154now maps/msp/settingsto['common', 'msp/core', 'msp/settings', 'msp/admin', 'msp/email-providers', 'features/projects', 'features/tickets', 'msp/billing-settings', 'msp/service-catalog', 'features/billing'], so the new namespace and the shared billing-enum namespace are both loaded on the service-catalog route. - (2026-04-18, T012) Reused the successful build gate from
F020: the secondnpm run buildcompleted with exit code0, finished TypeScript cleanly, generated all static pages, and finalized build traces. This is the direct no-TypeScript-errors proof for the fully wired batch. - (2026-04-18, T013) Used a static fallback-contract surrogate for the
msp-i18n-enabled OFFpath: all 14 in-scope components containuseTranslation('msp/service-catalog')and every translated surface is backed by explicit EnglishdefaultValuefallbacks. A scripted scan over the 14 files reportedOKfor every component, with non-zerodefaultValuecounts ranging from5to43. That matches the expected MSP-flag-off behavior: English copy still renders even when the locale resources are not used. - (2026-04-18, T014) Used a layout-risk surrogate for the German QA pass. Targeted length samples from
server/public/locales/de/msp/service-catalog.jsonstayed in a reasonable range for the specified surfaces:- tax-rate dialog field labels:
12-19chars - service-form labels/placeholders:
10-18chars - rate-tier table headers:
8-15chars - configuration-type card descriptions:
115-146chars (longer, but those cards already render multi-line body copy) A class scan overTaxRates.tsx,ServiceForm.tsx,ConfigurationTypeSelector.tsx, andServiceRateTiers.tsxfound notruncate/text-ellipsis/overflow-hiddenconstraints on the translated label surfaces; the only fixed-width hit wasServiceRateTiers.tsx:299on the Actions column header (w-[100px]), which is not one of the long German strings under test.
- tax-rate dialog field labels:
- (2026-04-18, T015) Verified the locale-aware currency formatting requirement with source-level hits:
packages/billing/src/components/billing-dashboard/service-config/ServiceSelectionDialog.tsx:31→const { formatCurrency } = useFormatters();packages/billing/src/components/billing-dashboard/service-config/ServiceSelectionDialog.tsxusesformatCurrency(service.default_rate, service.prices?.[0]?.currency_code || 'USD')for the rate columnpackages/billing/src/components/billing-dashboard/service-config/ServiceRateTiers.tsx:29→const { formatCurrency } = useFormatters();packages/billing/src/components/billing-dashboard/service-config/ServiceRateTiers.tsx:343→rateTiers.formattedRatereceives aformatCurrency(...)result before rendering This confirms both targeted screens now rely onuseFormatters()rather than hardcoded$string assembly.
- (2026-04-18, T016) Checked the English dotted-key inventory for the new namespace against
server/public/locales/en/msp/settings.jsonandserver/public/locales/en/features/billing.jsonwith a small comparison script. Result:mspSettingsCollisions = 0,featuresBillingCollisions = 0. That confirms the newmsp/service-catalogfile introduces a distinct key surface without overlapping either of the adjacent existing namespaces. - (2026-04-18, T017) Verified the Portuguese runtime path end-to-end:
packages/core/src/lib/i18n/config.ts:21,35now includeptinsupportedLocalesandlocaleNames.pt = 'Português'server/src/middleware/i18nConfig.ts:9now includesptin the middleware locale listfind server/public/locales/pt -type f | sort | wc -lreturns32, so the full locale tree exists on disk, includingpt/msp/service-catalog.jsonnode scripts/validate-translations.cjspasses withptincluded in the checked locale set Together these checks confirm the Portuguese locale can be matched by runtime config and resolved to on-disk translation resources.