chore(i18n): string sweep, shared intl test helper, toast lint guard - #2555
Open
innolope-dev wants to merge 2 commits into
Open
chore(i18n): string sweep, shared intl test helper, toast lint guard#2555innolope-dev wants to merge 2 commits into
innolope-dev wants to merge 2 commits into
Conversation
Every intl-using suite defined its own NextIntlClientProvider wrapper with the same locale/messages/timeZone triple. src/test-utils/intl.tsx now owns it and exports the three shapes the call sites actually need: IntlWrapper (composable, for suites that nest QueryClientProvider or a context provider), renderWithIntl (the common wrapper-option case), and renderHookWithIntl. Two suites had omitted `timeZone`, so next-intl fell back to the runner's system zone and their date formatting was machine-dependent; the shared wrapper pins UTC for everyone. renderHookWithIntl exists so useSumsubKycFlow.test.ts — a .ts file that could not use JSX — can drop its React.createElement workaround and the `eslint-disable react/no-children-prop` that came with it. ClientProviders.test.tsx deliberately keeps its own reference: it asserts on provider ORDERING, so the provider is the subject under test, not a fixture.
…asts
react/jsx-no-literals only inspects JSX children, so hardcoded copy kept
reaching users through toasts, placeholders, aria-labels and alt text. A
no-restricted-syntax selector now bans string literals as the first argument to
toast.*, appended to the EXISTING array rather than a new config block —
flat config replaces rule options when a later block names the same rule, so a
second entry would have silently disabled the four router.back selectors.
The rule is deliberately scoped to toast.* and not `throw new Error('…')`.
Every throw site was traced to its catch handler: they all funnel through
toFriendlyError to errors.genericSupport or a localized toast, so those strings
never reach a user. They are Sentry breadcrumbs and issue-grouping keys, and
translating them would fragment triage for no user-facing gain.
Extracted: the four QRScanner toasts (three already had keys and simply weren't
using them), both logout/login toasts and the two card-approval toasts, the
waitlist email placeholder, the two "Peanut Logo" aria-labels (reusing the
existing navigation.peanutLogoAlt), and two descriptive alt strings. The
QRScanner payment-method row keeps its brand names verbatim — those are proper
nouns — and only the EVM row, whose label is prose, moves to a key.
NavHeader gains an optional `titleKey`, because the receipt page at
/receipt/[entryId] is an async server component and this app has no server-side
next-intl setup (locale is resolved entirely client-side by AppIntlProvider), so
useTranslations is unavailable there. The mobile receipt page is a client
component and uses plain `title` with t().
Also replaced the clipboard read's message match. "There is no data on the
clipboard" is a hardcoded English literal inside @capacitor/clipboard's Java,
not an OS string — so this was never a locale bug, but it did couple us to a
dependency's copy, where a patch bump silently flips the branch. readClipboard()
now classifies the rejection and returns a reason.
The 'Enjoy Peanut!' sentinel is consolidated behind one isTestTransaction()
predicate. It compares backend data, so it is locale-safe, but it was repeated
in four places and one of them rendered the raw literal instead of the
transaction.enjoyPeanut key that already existed and was already translated.
The backend should own this flag — noted for follow-up.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
|
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:
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
innolope-dev
added a commit
that referenced
this pull request
Jul 28, 2026
…nner removal Carries the localization follow-up cherry-picked from the dev-based PRs (peanut-ui #2554 / #2555) plus the iOS-only removal of the pink beta feedback ribbon: - Backend errors now map to localized copy via a wire-code allow-list instead of being echoed in English; the Rain cooldown renders "try again in N minutes" in the user's language. - Locale-unsafe comparisons removed (a dead branch in the bank-withdraw flow that compared a translated string to an English literal). - Untranslated toasts, placeholders, aria-labels and alt text extracted. - 39 hand-rolled test intl wrappers collapsed into one shared helper. - The beta feedback ribbon is hidden on iOS native only; connectivity and maintenance banners still show, and Android/web are unchanged. App icon note: no icon change here. The BETA badge was dropped from the iOS icon in 1.0.38 and every build since (1.0.39, 1.0.40, 1.0.41) has carried the clean 1024px marketing icon.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Follow-up to #2447.
react/jsx-no-literalsonly inspects JSX children, so hardcoded copy kept reaching users through toasts, placeholders, aria-labels and alt text.The ticket was right about QRScanner
It named QRScanner as a known offender with "3–4 toasts". It had four, on
dev, and three of them had keys that already existed and simply weren't being used. (An earlier pass of mine looked at a divergent branch where they'd been extracted and wrongly concluded the ticket was stale.)The lint rule
A
no-restricted-syntaxselector banning string literals as the first argument totoast.error|success|info|warning|loading.Appended to the existing array, not a new config block — ESLint flat config replaces rule options when a later block names the same rule, so a second
no-restricted-syntaxentry would have silently disabled the fourrouter.backselectors. That array already sits underfiles: ['src/**/*.{ts,tsx}'], which is exactly the coverage needed, since every offender lives insrc/hooksorsrc/context— outside thejsx-no-literalsglobs.Verified it fires by planting a synthetic violation, not just by observing a green run.
Scoped to
toast.*, notthrow new Error('…'). Every throw site flagged in the ticket was traced to its catch handler: they all funnel throughtoFriendlyErrortoerrors.genericSupport, or into an already-localized toast. Those strings are Sentry breadcrumbs and issue-grouping keys — translating them would fragment triage for zero user-facing gain.Note the rule can't actually block a merge: the
eslintjob is stillcontinue-on-errorand absent fromci-success.needs. Left as-is deliberately; promoting it is a separate team decision.Shared test helper
src/test-utils/intl.tsxreplaces 39 hand-rolledNextIntlClientProviderwrappers (the ticket estimated ~15). Exports the three shapes call sites actually need:IntlWrapper(composable, for suites nestingQueryClientProvider),renderWithIntl, andrenderHookWithIntl.Two suites had omitted
timeZone, so next-intl fell back to the runner's system zone and their date formatting was machine-dependent — the shared wrapper pins UTC.renderHookWithIntlexists souseSumsubKycFlow.test.ts(a.tsfile, no JSX) can drop itsReact.createElementworkaround and theeslint-disable react/no-children-propthat came with it.ClientProviders.test.tsxdeliberately keeps its own reference — it asserts on provider ordering, so the provider is the subject under test.Extractions
Four QRScanner toasts, both logout/login toasts, two card-approval toasts, the waitlist email placeholder, two
"Peanut Logo"aria-labels (reusing the existingnavigation.peanutLogoAlt), two descriptive alt strings. The QRScanner payment-method row keeps its brand names verbatim — proper nouns — and only the EVM row, whose label is prose, moves to a key.NavHeadergains an optionaltitleKey:/receipt/[entryId]is an async server component and this app has no server-side next-intl setup (locale is resolved entirely client-side byAppIntlProvider), souseTranslationsis unavailable there.Two structural fixes
Clipboard. The match on
"There is no data on the clipboard"was not a locale bug — that string is a hardcoded English literal inside@capacitor/clipboard's Java (ClipboardPlugin.java:53), not an OS message. It was a dependency coupling: a plugin patch bump silently flips the branch and an empty clipboard starts reporting as a failure.readClipboard()now classifies the rejection and returns a reason.'Enjoy Peanut!'is consolidated behind oneisTestTransaction()predicate. It compares backend data, so it's locale-safe, but it was repeated in four places and one of them rendered the raw literal instead of thetransaction.enjoyPeanutkey that already existed and was already translated. The backend should own this flag — worth a ticket.Verification
pnpm typecheckclean · 174 suites / 2311 tests pass ·eslint0 errors ·prettier --check .cleanNote: peanut-ui's
pull_requestCI trigger only fires formain/dev/developbases, so this PR's checks come from thepushtrigger.