fix(i18n): localize backend-sourced status strings across all domains - #2604
Conversation
… codes Backend-sourced status and reason strings rendered in English for es-419/pt-BR users. Everything now keys off stable enums/codes — never backend prose — following the TYPE_LABEL_KEYS / rejectLabelCode patterns: - StatusBadge renders t() via an exhaustive STATUS_LABEL_KEYS table (common.status.*); the raw-passthrough default is gone and unknown values collapse to status.unknown. Its directory is no longer excluded from the jsx-no-literals guard — that ignore is how nine English literals shipped unnoticed. - PerkRewardReceipt's else-branch translates through the same table. - Reaper fail copy and intent-strategy display names move to a transaction-name key table: the transformer emits nameKey (+ ICU params for dynamic parts like usernames), render sites translate, and raw names remain as data-only fallbacks. The displayName string comparisons in TransactionDetailsHeaderCard now use discriminants instead of matching English copy. - CapabilityReason.code maps through a reason-code table with a fallback to the backend's userMessage where no code exists on the wire (identity.actionMessage, dispute prompts — backend follow-up). - Prop-literal stragglers keyed: ActivationCTAs spend prompts, the 'Adjustment' card row, the PIX maintenance badge, Sumsub fallbacks (server actions now also return a code, translated client-side). All new keys land in en / es-419 / pt-BR; the parity + ICU tests enforce the set stays complete.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Comment |
🧪 UI test report — ✅ all greenSuites
📊 Coverage (unit)
⏱ 10 slowest test cases
|
Code-analysis diffPainscore total: 6885.89 → 6908.74 (+22.85) 🆕 New findings (133)
…and 113 more. ✅ Resolved (126)
…and 106 more. 📈 Painscore deltas (top movers)
|
identity.actionMessage is a pure function of status on the backend (actionMessageFor), so the enum we already receive IS the discriminant: its presence keeps gating the reason card, the copy now comes from the catalog instead of rendering the English wire prose. DocsLink re-points /en/… hrefs at the app locale's marketing twin (app locales lowercase onto the marketing URL codes; the content fallback chains guarantee no 404). Covers every docs/help link in the app including the wallet nav's docs item. Rain's dispute evidenceRequestedMessage stays verbatim on purpose — it's case-specific free prose written by the processor's agents, with no enum to key off.
|
Follow-up pushed: KYC action messages now key off |
…the classifier's fix instruction The backend only emits document_rejected together with the self-heal classifier's fixable-specific text (which document to fix and how). Mapping the code onto a generic catalog line discarded that instruction. Render the BE prose until the classifier's stable action code reaches the wire (api-ts follow-up).
kushagrasarathe
left a comment
There was a problem hiding this comment.
Agent review (pr-review rulebook) — full-diff read, all four checklists
Scope reviewed: all 48 files (+805/−137), plus the backend code this PR's claims depend on (actionMessageFor, the capability resolver, the self-heal classifier).
What checked out
actionMessageForis genuinely a pure function of status (api-tsidentity.ts:81) — gating on presence and rendering catalog copy is safe; the updated KycStates test encodes the contract.- Catalog parity: identical key sets across en / es-419 / pt-BR; every new key present in all three.
REAPER_FAIL_KEYSexactly mirrorsREAPER_FAIL_COPY(9/9) with a safe generic fallback.- English-literal discriminants eliminated, not just translated —
nameKey === TRANSACTION_NAME_KEYS.sentViaLinkreplaces string comparison; zero literal comparisons remain at head. - eslint
Badges/**ignore removal is safe — the directory contains only StatusBadge, now literal-free. customText=''truthiness contract preserved; rawuserNamekept for data uses (test-tx marker, profile URL).- No deploy-order dependency:
reason.codealready ships from the resolver ondev; unknown codes fall back to BE prose. - Security: no new routes, deps, or secrets; diff matches the description exactly.
Finding (fixed in 121117b)
REASON_CODE_KEYS mapped document_rejected to a generic catalog line, but the backend only emits that code with the self-heal classifier's fixable-specific prose ("Your ID photo was blurry. Please upload a clearer photo." — classifier.ts, preferred at resolver.ts:1346). The generic line masked the fix instruction. Resolved by removing the mapping so those sites keep the BE prose, until the classifier's stable action code (REUPLOAD_ID, …) reaches the wire — api-ts follow-up, #1249 pattern.
Kept as-is (accepted): requirement-code entries stay mapped — their catalog copy is byte-identical to the resolver's messageForRequirement prose, so localization is a pure win in the common case; the rare fixable-classifier override on those codes is covered by the same api-ts follow-up. region_block also stays mapped — the BE's state/country/region word choice is lost, but the loss is cosmetic and localization wins.
Smell flags (fifth-bug rule, advisory)
Fix-commit density in the last 20 commits: useSumsubKycFlow.ts 12/20, transactionTransformer.ts 9/20, card/page.tsx 8/20, ActivationCTAs.tsx 8/20. The KYC-flow hook and the transformer are standing deep-dive candidates.
Verdict
TIER: T0 (external author; spans T2 KYC-gating surfaces)
PAIRED PR: none required — api-ts follow-up (classifier action codes on the wire) named, no deploy ordering
A. BREAKAGE: pass — document_rejected masking fixed in 121117b
B. PERF: pass — static table lookups, no new log/alert emission
C. QUALITY: pass with smell flags (fifth-bug rule, above)
D. SECURITY: pass — diff-vs-description exact match
TASK LINK: waived by reviewer
VERDICT: approve
Reviewed and approved by @kushagrasarathe (human, accountable); agent review advisory. Merge once CI is green on 121117b.
Why
Backend-sourced status strings showed in English for es-419 / pt-BR users — a transaction reading "pending", KYC status text, reason messages. The audit found the raw-English problem was narrower than expected (KYC status rendering, rail status and card status were already keyed or never rendered as text), but the sites that did leak were high-traffic: the shared
StatusBadge(transaction drawer, receipts, KYC drawer, success views) had nine hardcoded English literals plus a raw-passthrough default — sitting in a directory excluded from thejsx-no-literalsguard, which is exactly how it shipped unnoticed.What
Everything keys off stable enums/codes, never backend prose (#2554's rule), following the existing
TYPE_LABEL_KEYS/rejectLabelCode()patterns:StatusBadge→ exhaustiveSTATUS_LABEL_KEYS(as const satisfies Record<StatusType, string>) rendered viat()undercommon.status.*; unknown values collapse tostatus.unknowninstead of rendering the raw string. TheBadges/**lint ignore is removed. (Backend statuses were already normalized to the FE union bymapEntryStatusToUiStatus— no wire changes needed.)PerkRewardReceipt— the one genuine raw{status}interpolation in product code, now translated through the same table.REAPER_FAIL_COPY, 'Sent via Link', 'Card payment', …) — the transformer emits anameKey(+ ICU params for dynamic parts like usernames and bank names, which are data, not copy); render sites translate. The English-literaldisplayNamecomparisons inTransactionDetailsHeaderCardare replaced with discriminants (theisTestTransaction()precedent) so translating the labels can't break the logic.CapabilityReason.code→ key table with arejectLabelCode()-style fallback, covering the KYC/card/region reason-message sites (ApplicationStatusScreen, InitiateKycModal, UnlockedRegions, useWaitingOnProviderModal, ActivationCTAs). Where the wire carries no code (identity.actionMessage, Rain dispute prompts) the backend prose remains as fallback — adding those codes is an api-ts follow-up ([TASK-14942] feat(manteca-deposit): fix manteca deposit #1249 pattern).code, translated client-side — no server-side next-intl in this app).Out of scope, documented:
/receipt/[entryId]server-component titles (structurally blocked on server-side next-intl).Verification
customText=''means "no override" — truthiness preserved).tsc --noEmitclean; eslint on all touched files: 0 errors; catalog parity + ICU compilation tests green for all three locales.