Skip to content

fix(swap): preload i18n messages before initial render#6969

Open
crutch12 wants to merge 2 commits intocowprotocol:developfrom
crutch12:fix/i18n-preload-locale-messages
Open

fix(swap): preload i18n messages before initial render#6969
crutch12 wants to merge 2 commits intocowprotocol:developfrom
crutch12:fix/i18n-preload-locale-messages

Conversation

@crutch12
Copy link
Contributor

@crutch12 crutch12 commented Feb 4, 2026

Summary

<I18nProvider> renders null (white screen) if provided i18n instance is not activated. Thus we load messages and update i18n instance only after that "white screen" render.

It leads to the following problems:

  • we get extra rerenders when messages are loaded (initial render -> wait -> whole render)
  • we are not able to render the whole application in test environment (e.g. WithProviders) without waitFor/waitForNextUpdate usage
  • (in possible future) we are not able to render the whole application in SSR

Solution:

  • detect activeLocale and fetch localeMessages before initial application render

To Test

  1. (local only) React Profiler (Devtools) - "Reload and Start profiling" - Initial step should render almost the whole application tree
React Profiler Screenshot image
  1. i18n should work correctly

Background

  • Should we check jotaiStore.get(featureFlagsAtom) value?

@vercel
Copy link

vercel bot commented Feb 4, 2026

@crutch12 is attempting to deploy a commit to the cow Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 4, 2026

Walkthrough

The changes implement preloaded locale message support for the i18n system by introducing dynamic message loading infrastructure, updating i18n providers to accept messages, and modifying the app initialization flow to load locale messages before rendering.

Changes

Cohort / File(s) Summary
Locale message loading infrastructure
lib/localeMessages.ts, legacy/hooks/useActiveLocale.ts
Introduces loadLocaleMessages(), loadActiveLocaleMessages(), and dynamicActivate() functions for dynamic catalog loading; replaces initialLocale constant with getActiveLocale() function enabling preload without React context.
i18n provider enhancements
lib/i18n.tsx, i18n.tsx, cosmos.decorator.tsx
Adds messages prop to Provider and LanguageProvider components to accept preloaded messages; delegates locale activation to dynamicActivate() instead of inline catalog loading.
App initialization flow
cow-react/index.tsx
Introduces initApp() async function to load locale messages before rendering; updates Main to accept localeMessages prop and enforces initialization ordering.
Type declarations and test infrastructure
react-app-env.d.ts, test-utils.tsx
Adds ambient module declaration for .po file imports; updates test utilities to provide English messages to LanguageProvider.

Sequence Diagram

sequenceDiagram
    participant App as App Bootstrap
    participant Init as initApp()
    participant Loader as loadActiveLocaleMessages()
    participant Activator as dynamicActivate()
    participant Lib as i18n Library
    participant UI as Main (React)

    App->>Init: Start initialization
    Init->>Loader: Load messages for active locale
    Loader->>Lib: Import locale catalog (.po)
    Lib-->>Loader: Return Messages object
    Loader-->>Init: Return loaded messages
    Init->>Activator: Activate locale with messages
    Activator->>Lib: Load and activate catalog
    Lib-->>Activator: Locale activated
    Activator-->>Init: Activation complete
    Init->>UI: Render Main(localeMessages)
    UI->>UI: Pass messages to LanguageProvider
    LanguageProvider->>Lib: Provider propagates messages
    Lib-->>UI: i18n ready with locale strings
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • fairlighteth
  • shoom3301

Poem

🐰 Munching through locales with care and grace,
We load our messages in the right place,
Before the render, no blank page in sight,
i18n flows smooth—oh, what a delight!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The PR title accurately describes the main objective: preloading i18n messages before initial render to prevent white screen issues.
Description check ✅ Passed The PR description is comprehensive, covering the problem, solution, and testing instructions. However, it deviates from the template by not including a formal issue reference (Fixes #...) at the top.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Important

Action Needed: IP Allowlist Update

If your organization protects your Git platform with IP whitelisting, please add the new CodeRabbit IP address to your allowlist:

  • 136.113.208.247/32 (new)
  • 34.170.211.100/32
  • 35.222.179.152/32

Reviews will stop working after February 8, 2026 if the new IP is not added to your allowlist.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.


const catalog = await loadLocaleMessages(locale)

i18n.load(locale, catalog)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if there are no changes, it won't rerender the application

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/cowswap-frontend/src/lib/i18n.tsx (1)

22-27: ⚠️ Potential issue | 🟡 Minor

Route locale activation errors through the centralized logger.

console.error should be replaced with the shared logger for consistent formatting and routing.

As per coding guidelines: Replace stray console.log/debug/info with the centralized logger unless intentionally scoped diagnostics (prefixed) are required; use rg "console\\.(log|debug|info|warn|error)\\(" to remove or route through the logger.

🤖 Fix all issues with AI agents
In `@apps/cowswap-frontend/src/lib/i18n.tsx`:
- Around line 30-35: Remove the render-time side effects by deleting the
conditional block that calls i18n.load(locale, messages) and
i18n.activate(locale) inside the I18nProvider render; instead rely on the
pre-render bootstrap call loadActiveLocaleMessages() (from cow-react/index.tsx)
or the existing dynamicActivate() in the useEffect to initialize i18n
before/after mount—ensure I18nProvider only reads i18n.locale/messages and does
not mutate global i18n during render so initialization is consolidated into the
bootstrap or the useEffect path.

In `@apps/cowswap-frontend/src/lib/localeMessages.ts`:
- Around line 12-20: loadActiveLocaleMessages currently always uses
getActiveLocale(), causing a flash when i18n is disabled; change
loadActiveLocaleMessages to accept a boolean isInternationalizationEnabled, and
if false use DEFAULT_LOCALE instead of getActiveLocale() before calling
loadLocaleMessages; update callers (notably LanguageProvider) to pass
isInternationalizationEnabled so the function gates locale preloading when i18n
is off and avoids loading non-default locales.
🧹 Nitpick comments (1)
apps/cowswap-frontend/src/lib/localeMessages.ts (1)

23-41: Route console.error through the centralized logger.

This module now emits errors directly; please use the shared logger so logs are consistent and filterable.

As per coding guidelines, Use rg "console\\.(log|debug|info|warn|error)\\(" to remove or route through the logger.

Comment on lines +30 to 35
// if i18n is not activated (i18n.locale === ''), then I18nProvider renders null ("white screen") on initial render
// that's why we detect locale and load messages BEFORE initial render
if (!i18n.locale && messages) {
i18n.load(locale, messages)
i18n.activate(locale) // sets i18n.locale value, runs only one time
}
Copy link
Contributor

@coderabbitai coderabbitai bot Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's read the i18n.tsx file to see the full context
cat -n apps/cowswap-frontend/src/lib/i18n.tsx

Repository: cowprotocol/cowswap

Length of output: 1700


🏁 Script executed:

# Find where createRoot and render are called
rg -n "createRoot|ReactDOM\.render\(" apps/cowswap-frontend/src --type ts --type tsx

Repository: cowprotocol/cowswap

Length of output: 90


🏁 Script executed:

# Search for existing i18n initialization patterns
rg -n "dynamicActivate|loadActiveLocaleMessages|loadLocaleMessages" apps/cowswap-frontend/src

Repository: cowprotocol/cowswap

Length of output: 1248


🏁 Script executed:

# Check the initialization flow in cow-react/index.tsx around createRoot
sed -n '90,120p' apps/cowswap-frontend/src/cow-react/index.tsx

Repository: cowprotocol/cowswap

Length of output: 883


🏁 Script executed:

# Look for createRoot and initial render
rg -n "createRoot|root.render\|ReactDOM" apps/cowswap-frontend/src/cow-react/index.tsx -A 5

Repository: cowprotocol/cowswap

Length of output: 715


Move i18n initialization out of render—consolidate into pre-render bootstrap or useEffect.

The i18n.load and i18n.activate calls (lines 30–35) execute during component render, mutating global state. The guard condition !i18n.locale prevents re-execution within a render cycle, but does not eliminate the anti-pattern; in StrictMode or with concurrent rendering enabled, side effects during render remain unsafe and unreliable.

Since loadActiveLocaleMessages() already runs pre-render in cow-react/index.tsx (line 106) and dynamicActivate() already runs in useEffect (line 23), this render-time initialization is redundant and should be removed. Consolidate the initialization path: either pass fully-initialized i18n state before rendering, or defer to the existing useEffect pattern.

🤖 Prompt for AI Agents
In `@apps/cowswap-frontend/src/lib/i18n.tsx` around lines 30 - 35, Remove the
render-time side effects by deleting the conditional block that calls
i18n.load(locale, messages) and i18n.activate(locale) inside the I18nProvider
render; instead rely on the pre-render bootstrap call loadActiveLocaleMessages()
(from cow-react/index.tsx) or the existing dynamicActivate() in the useEffect to
initialize i18n before/after mount—ensure I18nProvider only reads
i18n.locale/messages and does not mutate global i18n during render so
initialization is consolidated into the bootstrap or the useEffect path.

Copy link
Contributor Author

@crutch12 crutch12 Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's intended - i18n instance is a singleton

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@crutch12, understood. The synchronous initialization before I18nProvider renders is intentional to avoid the white screen issue. I appreciate the clarification!


🧠 Learnings used
Learnt from: CR
Repo: cowprotocol/cowswap PR: 0
File: apps/cowswap-frontend/AGENTS.md:0-0
Timestamp: 2026-01-27T19:05:52.016Z
Learning: Use `yarn nx run cowswap-frontend:i18n` to manage i18n translations for cowswap-frontend

Learnt from: CR
Repo: cowprotocol/cowswap PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-27T19:05:35.655Z
Learning: Applies to **/*.{ts,tsx} : Inline render factories used to dodge `react/no-unstable-nested-components` warnings are a red flag

Learnt from: CR
Repo: cowprotocol/cowswap PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-27T19:05:35.655Z
Learning: Applies to **/*.{ts,tsx} : Stabilize hook dependencies and avoid recreating objects in render in React

Learnt from: CR
Repo: cowprotocol/cowswap PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-27T19:05:35.655Z
Learning: Applies to **/*.{ts,tsx} : Avoid inline IIFEs/closures inside `.map()`; extract helpers outside render

Learnt from: CR
Repo: cowprotocol/cowswap PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-27T19:05:35.655Z
Learning: Applies to **/*.{ts,tsx} : Never declare components inside render bodies or rely on `render*`/`get*` helpers that return JSX; hoist subcomponents to module scope

Learnt from: CR
Repo: cowprotocol/cowswap PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-27T19:05:35.655Z
Learning: Applies to **/*.{ts,tsx} : Replace inline factories with extracted components or memoized callbacks; obey `react/no-unstable-nested-components`

Learnt from: CR
Repo: cowprotocol/cowswap PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-27T19:05:35.655Z
Learning: Applies to **/*.{ts,tsx} : Hook dependency cascades are a red flag - verify control flow before tweaking dependency arrays

Learnt from: CR
Repo: cowprotocol/cowswap PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-27T19:05:35.655Z
Learning: Applies to **/*.{ts,tsx} : Complicated JS logic to prevent visual glitches is a red flag - prioritize CSS-based solutions first

Learnt from: CR
Repo: cowprotocol/cowswap PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-27T19:05:35.655Z
Learning: Applies to **/*.{ts,tsx} : No side effects in render - use `useEffect`, `useCallback`, or event handlers instead

Learnt from: CR
Repo: cowprotocol/cowswap PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-27T19:05:35.655Z
Learning: Applies to **/*.{ts,tsx} : Complex bespoke state managers are a red flag - investigate the trigger chain instead of layering abstractions

Comment on lines +12 to +20
export async function loadActiveLocaleMessages(): Promise<Messages | undefined> {
const activeLocale = getActiveLocale()
try {
const messages = await loadLocaleMessages(activeLocale)
return messages
} catch (error) {
console.error('Could not load locale file: ' + activeLocale, error)
return undefined
}
Copy link
Contributor

@coderabbitai coderabbitai bot Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

grep -R "initApp" -n apps/cowswap-frontend/src

Repository: cowprotocol/cowswap

Length of output: 214


🏁 Script executed:

cat -n apps/cowswap-frontend/src/initApp.ts

Repository: cowprotocol/cowswap

Length of output: 131


🏁 Script executed:

rg -n "loadActiveLocaleMessages" -C3 -t ts apps/cowswap-frontend/src

Repository: cowprotocol/cowswap

Length of output: 2043


🏁 Script executed:

rg -n "i18nEnabled" -C3 -t ts apps/cowswap-frontend/src

Repository: cowprotocol/cowswap

Length of output: 45


🏁 Script executed:

cat -n apps/cowswap-frontend/src/providers/LanguageProvider.tsx

Repository: cowprotocol/cowswap

Length of output: 151


🏁 Script executed:

cat -n apps/cowswap-frontend/src/lib/localeMessages.ts | sed -n '1,30p'

Repository: cowprotocol/cowswap

Length of output: 1356


🏁 Script executed:

fd WithLDProvider -t f -I -p apps/cowswap-frontend/src

Repository: cowprotocol/cowswap

Length of output: 138


Gate locale preloading on the i18n feature flag

loadActiveLocaleMessages always loads getActiveLocale(), but when i18n is disabled LanguageProvider resets to DEFAULT_LOCALE, causing a flash of the non-default locale. Pass isInternationalizationEnabled into loadActiveLocaleMessages and fallback to DEFAULT_LOCALE:

🛠️ Suggested adjustment
-export async function loadActiveLocaleMessages(): Promise<Messages | undefined> {
-  const activeLocale = getActiveLocale()
+export async function loadActiveLocaleMessages(
+  isInternationalizationEnabled = true,
+): Promise<Messages | undefined> {
+  const activeLocale = isInternationalizationEnabled ? getActiveLocale() : DEFAULT_LOCALE
🤖 Prompt for AI Agents
In `@apps/cowswap-frontend/src/lib/localeMessages.ts` around lines 12 - 20,
loadActiveLocaleMessages currently always uses getActiveLocale(), causing a
flash when i18n is disabled; change loadActiveLocaleMessages to accept a boolean
isInternationalizationEnabled, and if false use DEFAULT_LOCALE instead of
getActiveLocale() before calling loadLocaleMessages; update callers (notably
LanguageProvider) to pass isInternationalizationEnabled so the function gates
locale preloading when i18n is off and avoids loading non-default locales.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's intended - we have no isInternationalizationEnabled value before initial render. This won't affect the application

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@crutch12, thank you for the clarification! That makes sense—since loadActiveLocaleMessages() runs in initApp() before the initial render, the feature flags (including isInternationalizationEnabled) haven't been initialized yet. I understand that this early loading is intentional to prevent the white screen and extra rerenders you're addressing in this PR.


✏️ Learnings added
Learnt from: crutch12
Repo: cowprotocol/cowswap PR: 6969
File: apps/cowswap-frontend/src/lib/localeMessages.ts:12-20
Timestamp: 2026-02-04T18:23:19.855Z
Learning: In apps/cowswap-frontend/src/lib/localeMessages.ts, the loadActiveLocaleMessages() function runs before initial render (in initApp()), at which point feature flags like isInternationalizationEnabled are not yet available. This early locale loading is intentional to avoid white screens and extra rerenders.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: CR
Repo: cowprotocol/cowswap PR: 0
File: apps/cowswap-frontend/AGENTS.md:0-0
Timestamp: 2026-01-27T19:05:52.016Z
Learning: Use `yarn nx run cowswap-frontend:i18n` to manage i18n translations for cowswap-frontend

Learnt from: alfetopito
Repo: cowprotocol/cowswap PR: 6234
File: libs/tokens/src/index.ts:1-4
Timestamp: 2025-09-11T08:25:51.460Z
Learning: In the cowprotocol/cowswap project, there is currently no SSR (Server-Side Rendering) support, so localStorage access at module import time does not cause SSR-related issues.

Learnt from: limitofzero
Repo: cowprotocol/cowswap PR: 6770
File: apps/cowswap-frontend/src/modules/tokensList/hooks/useAddListImport.ts:27-31
Timestamp: 2025-12-30T18:49:03.377Z
Learning: In apps/cowswap-frontend/src/modules/tokensList/hooks/useAddListImport.ts, when restrictedLists.isLoaded is false or geoStatus.isLoading is true, the code intentionally proceeds with the import immediately without blocking. This allows imports to proceed during loading states, deferring consent checks to trade time when necessary data isn't yet available.

Learnt from: CR
Repo: cowprotocol/cowswap PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-27T19:05:35.655Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Hardcoded environment-specific lists/toggles when shared config/enums exist are a red flag

Learnt from: CR
Repo: cowprotocol/cowswap PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-27T19:05:35.655Z
Learning: Applies to **/*.{ts,tsx} : Detect whether data originated locally or from the API and adapt derived values (e.g., bridge output tokens)

Learnt from: CR
Repo: cowprotocol/cowswap PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-27T19:05:35.655Z
Learning: Applies to **/*.{ts,tsx} : Complicated JS logic to prevent visual glitches is a red flag - prioritize CSS-based solutions first

Learnt from: fairlighteth
Repo: cowprotocol/cowswap PR: 5768
File: apps/cow-fi/components/LearnPageComponent.tsx:184-185
Timestamp: 2025-05-28T16:50:12.273Z
Learning: In apps/cow-fi/components/LearnPageComponent.tsx, the user prefers to keep the inconsistent link behavior where featured articles always open in new tabs (target="_blank") while media coverage links conditionally open in new tabs based on the linkExternal flag. This inconsistency should not be flagged as an issue in future reviews.

Learnt from: CR
Repo: cowprotocol/cowswap PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-27T19:05:35.655Z
Learning: Applies to **/*.{ts,tsx} : Use shared constants when referencing protocol names or disclaimers to keep copy consistent across the bridge UX

Learnt from: CR
Repo: cowprotocol/cowswap PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-27T19:05:35.655Z
Learning: Applies to **/*.{ts,tsx} : Hook dependency cascades are a red flag - verify control flow before tweaking dependency arrays

Learnt from: CR
Repo: cowprotocol/cowswap PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-27T19:05:35.655Z
Learning: Applies to **/*.{ts,tsx} : Drive feature toggles from real data comparisons (e.g., compare chain IDs to determine bridging) instead of heuristics

@elena-zh elena-zh requested a review from a team February 5, 2026 08:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants