Skip to content

Load the FAQ articles on demand instead of in every route's locale bundle - #1616

Merged
feruzm merged 1 commit into
developfrom
perf/lazy-faq-locale
Aug 21, 2026
Merged

Load the FAQ articles on demand instead of in every route's locale bundle#1616
feruzm merged 1 commit into
developfrom
perf/lazy-faq-locale

Conversation

@feruzm

@feruzm feruzm commented Aug 21, 2026

Copy link
Copy Markdown
Member

en-US.json is the only eagerly bundled locale, and its FAQ articles (static.faq.*-header / *-body: 262 keys, about 17 KB gzipped, a quarter of the file) shipped in first-load JS on every route although only the FAQ surfaces render them.

A webpack loader (features/i18n/faq-split.js) now splits that file at build time: the plain import gets the locale without the articles and a ?faq import gets only them, so the JSON on disk stays whole and Crowdin keeps one source file. The other locales are already loaded on demand as whole files and need no change.

ensureFaqLoaded merges the English articles back into the translation namespace (and loads them alongside any other language, since English is the per-key fallback), so every existing i18next.t("static.faq.…") call keeps working once it has resolved. The FAQ and About pages await it on the server; the FAQ page hands the English articles to its client components through <FaqResources> so they hydrate with the strings the server rendered; the help-center search, the decks FAQ column and the two perks explainers render their FAQ strings once useFaqTranslations reports them present. Only English is ever primed from the server: the other locales belong to loadLocale as whole files, and its guard now probes a core key so a partial bundle can never pass for the full file (this was the one real finding in review).

Measured on the production build: the eager en-US chunk goes from 77 KB to 61 KB gzipped on every route, the articles become a 17 KB on-demand chunk that no route lists in first-load, and the feed route's first-load JS is 779 KB gzipped. Trade-off stated plainly: /faq itself now carries the English articles once more inside its RSC payload (about 17 KB gzipped) so its client components can hydrate and search without a flash, which is a small loss on that one page against the saving everywhere else.

Test plan

  • faq-split.spec.ts: the loader moves every article and nothing else, keeps the small FAQ keys, serves core vs ?faq by resource query; on the real i18next instance the articles are reported missing, merged back, exposed for the client, primed idempotently, loaded alongside another language, and a partial bundle no longer stops loadLocale from fetching the whole file. Full suite (334 files, 3,160 tests), tsc --noEmit and next lint clean.
  • Production build served locally and checked in a browser: /faq server-renders the article text, the category lists hydrate with real headers and client-side search over bodies works; /faq?lang=es renders Spanish on the server and the client switch loads the whole Spanish locale (UI strings included); /about and /perks/points render their FAQ text; the home page loads no FAQ chunk. The React Made waves form actions visible permanently #418 hydration warning seen on /faq and /perks/points, and the raw what-gift-button-means key on /faq, reproduce on the current develop build as well and are unrelated to this change.

Closes #1598

@coderabbitai

coderabbitai Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@feruzm, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 58 minutes

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

Wait for the limit to reset, then comment @coderabbitai review or push new commits to the PR.

An organization admin can change what happens after included review limits in Billing.

How do review limits work?

CodeRabbit enforces per-developer PR review limits within each organization.

For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ead39c33-4973-4d57-95af-d1e55c922cd3

📥 Commits

Reviewing files that changed from the base of the PR and between 3afb714 and 44a9b62.

📒 Files selected for processing (15)
  • apps/web/next.config.js
  • apps/web/src/app/(staticPages)/about/page.tsx
  • apps/web/src/app/(staticPages)/faq/page.tsx
  • apps/web/src/app/decks/_components/columns/deck-faq-column.tsx
  • apps/web/src/app/perks/points/_components/points-basic-info.tsx
  • apps/web/src/app/perks/promote-post/_components/promote-post-intro.tsx
  • apps/web/src/features/ecency-center/sections/center-faq.tsx
  • apps/web/src/features/i18n/faq-resources.tsx
  • apps/web/src/features/i18n/faq-split.js
  • apps/web/src/features/i18n/faq.ts
  • apps/web/src/features/i18n/index.ts
  • apps/web/src/features/i18n/json-query.d.ts
  • apps/web/src/features/i18n/use-faq-translations.ts
  • apps/web/src/specs/features/i18n/faq-split.spec.ts
  • apps/web/vitest.config.mts
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch perf/lazy-faq-locale

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.

@qodo-code-review

qodo-code-review Bot commented Aug 21, 2026

Copy link
Copy Markdown

PR Summary by Qodo

Lazy-load en-US FAQ translations via build-time locale split

✨ Enhancement 🐞 Bug fix 🧪 Tests ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Split en-US FAQ article strings into an on-demand chunk to reduce first-load JS.
• Add SSR/client priming so existing i18next.t(static.faq.*) calls keep working.
• Harden locale-loading guards and add comprehensive tests for loader + runtime behavior.
Diagram

graph TD
  A["Next.js build"] --> B(["faq-split webpack loader"]) --> C["en-US.json (core)"] --> F[("i18next translation bundle")]
  E(["ensureFaqLoaded(lang)"]) --> D["en-US.json?faq (articles)"] --> F
  G["SSR pages (FAQ/About)"] --> E
  G --> I(["FaqResources / useFaqTranslations"]) --> F
  H["Client UI (search/decks/perks)"] --> I --> E

  subgraph Legend
    direction LR
    _page["Page/UI"] ~~~ _svc(["Runtime helper"]) ~~~ _db[("Resource store")] 
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Move FAQ articles to a separate locale file (e.g., faq.en-US.json)
  • ➕ No custom webpack loader or query-based module resolution
  • ➕ Clear ownership and caching semantics per translation domain
  • ➖ Splits Crowdin source-of-truth into multiple files (workflow overhead)
  • ➖ Requires new translation loading conventions and likely wider refactors
2. Fetch FAQ article strings at runtime (HTTP) instead of bundling
  • ➕ Minimizes JS bundle impact for all routes
  • ➕ Allows independent caching and invalidation of FAQ content
  • ➖ Adds network dependency and more failure modes
  • ➖ More complex SSR/hydration coordination to prevent flashing keys
3. i18next multi-namespace split (core vs faq namespaces)
  • ➕ Uses i18next concepts directly (namespaces) rather than key-based probing
  • ➕ Potentially cleaner separation for future large domains
  • ➖ Requires touching many call sites (static.faq.*) or configuring namespace fallbacks
  • ➖ Does not preserve the current single-file Crowdin model without extra tooling

Recommendation: The current approach is the best fit given the explicit constraint of keeping a single Crowdin source file while removing FAQ article payload from the eagerly bundled en-US locale. The build-time split plus runtime merge preserves existing i18next.t("static.faq…") call sites, avoids additional network requests, and keeps SSR/client output aligned via server preloading and client priming. The key follow-up risk to watch is i18next bundle “presence” checks; this PR already mitigates it by probing a core key in loadLocale to avoid partial-bundle false positives.

Files changed (15) +392 / -13

Enhancement (9) +185 / -11
page.tsxEnsure FAQ strings are available during About page SSR +4/-1

Ensure FAQ strings are available during About page SSR

• Converts the About page to an async server component and awaits ensureFaqLoaded("en-US") before rendering. Prevents missing FAQ header strings now that articles are no longer in the eager en-US bundle.

apps/web/src/app/(staticPages)/about/page.tsx

page.tsxPreload FAQ translations on the server and prime client hydration +18/-1

Preload FAQ translations on the server and prime client hydration

• Loads English FAQ articles (and optionally requested language via ?lang) during SSR using ensureFaqLoaded. Injects <FaqResources> with English article resources so client components hydrate with the same strings the server rendered.

apps/web/src/app/(staticPages)/faq/page.tsx

deck-faq-column.tsxGate deck FAQ column search/rendering on FAQ translation readiness +6/-2

Gate deck FAQ column search/rendering on FAQ translation readiness

• Adds useFaqTranslations() and only filters/renders FAQ article links once the on-demand FAQ strings are registered. Prevents rendering raw i18next keys during initial client load.

apps/web/src/app/decks/_components/columns/deck-faq-column.tsx

points-basic-info.tsxHide points explainer until FAQ article translations are loaded +6/-1

Hide points explainer until FAQ article translations are loaded

• Uses useFaqTranslations() and conditionally renders the FAQ-backed explainer HTML only once translations are available. Avoids flashing raw keys for static.faq.what-is-points-body.

apps/web/src/app/perks/points/_components/points-basic-info.tsx

promote-post-intro.tsxHide promotion explainer until FAQ article translations are loaded +8/-3

Hide promotion explainer until FAQ article translations are loaded

• Adds useFaqTranslations() and conditionally renders the FAQ-backed explainer HTML. Ensures the client doesn’t hydrate with untranslated static.faq.how-promotion-work-body keys.

apps/web/src/app/perks/promote-post/_components/promote-post-intro.tsx

center-faq.tsxDelay help-center FAQ search/results until FAQ translations exist +6/-3

Delay help-center FAQ search/results until FAQ translations exist

• Introduces useFaqTranslations() and blocks both search filtering and list rendering until FAQ article strings are loaded. Prevents key-echo behavior from corrupting search and UI output.

apps/web/src/features/ecency-center/sections/center-faq.tsx

faq-resources.tsxClient priming component to register server-provided English FAQ articles +22/-0

Client priming component to register server-provided English FAQ articles

• Adds a client component that synchronously calls primeEnglishFaq(resources) during hydration render. Designed to keep FAQ page client components consistent with SSR output without post-hydration flashing.

apps/web/src/features/i18n/faq-resources.tsx

faq.tsRuntime API for loading/priming FAQ article translations on demand +79/-0

Runtime API for loading/priming FAQ article translations on demand

• Adds ensureFaqLoaded() with per-language in-flight deduping, and isFaqLoaded() probing via a known FAQ article key. Provides primeEnglishFaq() for synchronous English-only registration and getEnglishFaqResources() for SSR-to-client handoff.

apps/web/src/features/i18n/faq.ts

use-faq-translations.tsAdd client hook to load FAQ articles and expose readiness +36/-0

Add client hook to load FAQ articles and expose readiness

• Introduces a hook that calls ensureFaqLoaded() on mount and on i18n language changes, returning a boolean when FAQ article resources are present. Enables components to avoid rendering raw keys pre-load.

apps/web/src/features/i18n/use-faq-translations.ts

Bug fix (1) +4 / -1
index.tsPrevent partial bundles from satisfying loadLocale and export FAQ helpers +4/-1

Prevent partial bundles from satisfying loadLocale and export FAQ helpers

• Changes loadLocale’s early-return guard to probe a core key (g) rather than bundle existence, preventing FAQ-only partial registration from blocking full locale fetch. Re-exports FAQ helper functions from the i18n index.

apps/web/src/features/i18n/index.ts

Tests (1) +130 / -0
faq-split.spec.tsAdd tests for FAQ locale splitting, merging, priming, and locale-guard behavior +130/-0

Add tests for FAQ locale splitting, merging, priming, and locale-guard behavior

• Validates the loader’s split is exact (moves only -header/-body keys) and that core namespaces remain unchanged. Exercises real i18next behavior for missing/merged resources, SSR handoff payload shape, idempotent priming, and the loadLocale guard against partial bundles.

apps/web/src/specs/features/i18n/faq-split.spec.ts

Other (4) +73 / -1
next.config.jsAdd webpack rule to split en-US FAQ articles via custom loader +11/-0

Add webpack rule to split en-US FAQ articles via custom loader

• Registers a webpack loader for features/i18n/locales/en-US.json that serves either the core locale or FAQ-articles-only based on the ?faq resource query. Applies to both server and client bundles to keep SSR and hydration consistent.

apps/web/next.config.js

faq-split.jsWebpack loader to split FAQ article keys from en-US locale at build time +41/-0

Webpack loader to split FAQ article keys from en-US locale at build time

• Implements splitFaq() to separate static.faq keys ending in -header/-body into a dedicated payload. loader() emits either the core locale or articles-only JSON depending on resourceQuery, preserving a single on-disk locale for Crowdin.

apps/web/src/features/i18n/faq-split.js

json-query.d.tsAdd TypeScript module declaration for *.json?faq imports +7/-0

Add TypeScript module declaration for *.json?faq imports

• Declares a loose module type for JSON imports with the ?faq query so TS accepts dynamic imports used for the split FAQ chunk.

apps/web/src/features/i18n/json-query.d.ts

vitest.config.mtsTeach Vitest/Vite to resolve en-US.json?faq imports +14/-1

Teach Vitest/Vite to resolve en-US.json?faq imports

• Adds a pre-resolve plugin that maps en-US.json?faq to the plain en-US.json file because Vite lacks the webpack loader. Keeps tests running while faq.ts normalizes the module shape at runtime.

apps/web/vitest.config.mts

@greptile-apps

greptile-apps Bot commented Aug 21, 2026

Copy link
Copy Markdown

Greptile Summary

The PR removes English FAQ articles from the eagerly bundled locale and loads them only for FAQ-dependent surfaces.

  • Adds a webpack loader that separates core English translations from FAQ article content.
  • Adds on-demand loading, locale-aware readiness, bounded retries, and server-to-client FAQ resource priming.
  • Updates FAQ consumers to wait for article translations before rendering.
  • Adds coverage for splitting, resource merging, fallback loading, and partial locale bundles.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
apps/web/next.config.js Registers the targeted English locale loader for both core and query-qualified FAQ imports.
apps/web/src/features/i18n/faq-split.js Splits FAQ article headers and bodies from the eagerly loaded English locale while retaining core FAQ strings.
apps/web/src/features/i18n/faq.ts Implements deduplicated on-demand FAQ loading, English fallback registration, and server resource extraction.
apps/web/src/features/i18n/use-faq-translations.ts Exposes current-locale readiness and retries transient loading failures while preventing stale loads from marking another locale ready.
apps/web/src/features/i18n/faq-resources.tsx Synchronously primes client-side English FAQ resources supplied by the FAQ server page.
apps/web/src/app/(staticPages)/faq/page.tsx Preloads server FAQ translations and transfers English article resources for hydration and client-side search.
apps/web/src/specs/features/i18n/faq-split.spec.ts Covers locale splitting, resource restoration, fallback loading, idempotent priming, and full-locale loading after partial registration.

Sequence Diagram

sequenceDiagram
  participant Surface as FAQ-dependent surface
  participant Hook as useFaqTranslations
  participant Loader as ensureFaqLoaded
  participant Locale as i18next resources
  Surface->>Hook: Request readiness for active language
  Hook->>Loader: Load active-language FAQ resources
  alt English
    Loader->>Loader: Import en-US.json?faq
  else Other locale
    Loader->>Locale: Load whole locale bundle
    Loader->>Loader: Load English fallback articles
  end
  Loader->>Locale: Merge translation resources
  Loader-->>Hook: Load settled
  Hook->>Locale: Probe current active language
  Hook-->>Surface: Render when ready
Loading

Reviews (2): Last reviewed commit: "Load the FAQ articles on demand instead ..." | Re-trigger Greptile

Comment thread apps/web/src/features/i18n/use-faq-translations.ts Outdated
Comment thread apps/web/src/features/i18n/use-faq-translations.ts
@feruzm
feruzm force-pushed the perf/lazy-faq-locale branch from edc0f94 to 4a2b50a Compare August 21, 2026 09:25
@qodo-code-review

qodo-code-review Bot commented Aug 21, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (1) 📜 Skill insights (0)

Grey Divider


Action required

1. useFaqTranslations missing effect deps ✗ Dismissed 📜 Skill insight ≡ Correctness
Description
useFaqTranslations uses i18n, isFaqLoaded, and ensureFaqLoaded inside a useEffect but
provides an empty dependency array, which violates the hook-deps requirement and risks stale
closures if any referenced values change. This can lead to the FAQ readiness state not updating
correctly in future refactors or different runtime setups.
Code

apps/web/src/features/i18n/use-faq-translations.ts[33]

+  }, []);
Relevance

●●● Strong

Explicit hook-dependency compliance rule makes this a deterministic, low-risk fix typically
accepted.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 2668377 requires hook dependency arrays to include all referenced values. The new
useFaqTranslations hook registers an effect that references i18n, ensureFaqLoaded, and
isFaqLoaded, but the dependency array is [].

apps/web/src/features/i18n/use-faq-translations.ts[15-33]
Skill: code-review

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`useFaqTranslations()` references values inside `useEffect` while using an empty dependency array (`[]`), which violates the requirement that hook dependency arrays be complete.

## Issue Context
The effect references imported functions (`ensureFaqLoaded`, `isFaqLoaded`) and the `i18n` instance. Even if these are currently stable, the rule requires complete deps to prevent stale-closure bugs during future changes.

## Fix Focus Areas
- apps/web/src/features/i18n/use-faq-translations.ts[15-33]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. New any in vitest plugin 📘 Rule violation ⚙ Maintainability
Description
apps/web/vitest.config.mts introduces any types (this: any and as any) in new code,
violating the ban on new any usage. This reduces type safety and can mask real typing issues in
the test/build tooling layer.
Code

apps/web/vitest.config.mts[R28-31]

+  resolveId(this: any, id: string, importer: string | undefined) {
+    if (!id.endsWith('en-US.json?faq')) return null;
+    return this.resolve(id.replace('?faq', ''), importer, { skipSelf: true });
+  }
Relevance

●●● Strong

Team has accepted removing explicit any in new test/config code under no-any policy.

PR-#1572

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 2668119 forbids introducing any in new/modified TypeScript. The new Vite plugin
uses resolveId(this: any, ...) and is also inserted into the plugins list via an as any cast.

Rule 2668119: Disallow implicit and any types in new TypeScript code
apps/web/vitest.config.mts[25-37]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
New code in `vitest.config.mts` introduces `any` (`this: any` and `faqQuery as any`), which violates the rule disallowing new `any`/implicit-any usage in changed TypeScript.

## Issue Context
The file comment notes plugin typing mismatches due to dual Vite versions; however, the code can still avoid `any` by using `unknown` plus a narrow cast for the minimal `this.resolve(...)` shape, or by using a compatible Vite `Plugin`/`PluginContext` type without falling back to `any`.

## Fix Focus Areas
- apps/web/vitest.config.mts[25-37]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Stale load marks language ready 🐞 Bug ≡ Correctness
Description
useFaqTranslations can set ready=true when an earlier locale load resolves even if
i18n.language has since changed and the current language is still loading. This can cause
consumers to render fallback/raw keys or stale-language FAQ content and may remain stuck if the
later “current language” completion tries to set ready to true again and React skips the no-op
update.
Code

apps/web/src/features/i18n/use-faq-translations.ts[R20-22]

+        .then(() => {
+          if (!cancelled) setReady(true);
+        })
Relevance

●●● Strong

Recent accepted async state-lifecycle fixes show team accepts guarding stale async completions.

PR-#1579

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The hook kicks off asynchronous, per-language loads triggered by a mutable global i18next language
(e.g., via languageChanged), but the continuation after each promise resolves only guards against
component unmount and does not verify that the resolved load corresponds to the currently active
language (or latest request). Because resource loads complete independently, an older request can
finish after a language switch and still mark readiness true; consumers rely on this single boolean
gate to proceed with translating/searching using the current global language, so readiness from an
outdated load does not guarantee the active language’s FAQ bundle is actually present.

apps/web/src/features/i18n/use-faq-translations.ts[15-32]
apps/web/src/features/i18n/faq.ts[45-62]
apps/web/src/features/i18n/index.ts[165-168]
apps/web/src/app/perks/points/_components/points-basic-info.tsx[12-35]
apps/web/src/features/ecency-center/sections/center-faq.tsx[14-41]
apps/web/src/features/i18n/faq.ts[29-31]
apps/web/src/features/i18n/faq.ts[45-63]
apps/web/src/app/perks/points/_components/points-basic-info.tsx[33-35]
apps/web/src/app/decks/_components/columns/deck-faq-column.tsx[23-32]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`useFaqTranslations` can incorrectly mark itself `ready` when an older locale’s FAQ resource load completes after i18next has already switched languages, so consumers may render/search FAQ strings for the new locale before its bundle is available (showing fallback English, raw keys, incorrect results, or stale-language content).

## Issue Context
The hook starts asynchronous loads per language in response to language changes, but it only cancels/guards on unmount and unconditionally sets `ready=true` on any completion without checking that the completion matches the active language (or the latest in-flight request). During rapid language switches, an earlier request can resolve later and flip readiness early; when the real current-language load finishes, calling `setReady(true)` again may be a no-op, potentially leaving the UI stuck until another render. Fix by associating each load with the language (or a monotonically increasing request generation) and only updating readiness when the resolved request still matches the active/latest language, recomputing readiness for the active language rather than unconditionally setting it to true.

## Fix Focus Areas
- apps/web/src/features/i18n/use-faq-translations.ts[15-32]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

4. Failed loads stay hidden 🐞 Bug ☼ Reliability
Description
The hook suppresses every ensureFaqLoaded rejection without recording an error or scheduling a
retry, while all new consumers hide their FAQ content whenever ready remains false. A transient
chunk or locale request failure therefore leaves the affected FAQ list or explainer blank for the
component's lifetime, rather than recovering or showing an error.
Code

apps/web/src/features/i18n/use-faq-translations.ts[R23-25]

+        .catch(() => {
+          /* the strings stay hidden; nothing else to do */
+        });
Relevance

● Weak

Team recently rejected adding separate UI/error-state handling for failed async checks in similar
hook.

PR-#1528

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The catch block intentionally performs no state update, retry, or reporting. Each updated surface
gates article rendering on the hook's boolean, so a rejected load leaves those elements absent with
no recovery path until the effect is retriggered by remount or another language change.

apps/web/src/features/i18n/use-faq-translations.ts[15-33]
apps/web/src/app/decks/_components/columns/deck-faq-column.tsx[19-32]
apps/web/src/app/decks/_components/columns/deck-faq-column.tsx[65-73]
apps/web/src/app/perks/points/_components/points-basic-info.tsx[30-35]
apps/web/src/app/perks/promote-post/_components/promote-post-intro.tsx[19-23]
apps/web/src/features/ecency-center/sections/center-faq.tsx[71-87]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A rejected lazy FAQ load is swallowed and leaves FAQ content permanently hidden until unmount or another language change.

## Issue Context
Add a recoverable error/loading state or bounded retry path. Consumers should provide a retry/error fallback rather than silently rendering no article content indefinitely.

## Fix Focus Areas
- apps/web/src/features/i18n/use-faq-translations.ts[12-35]
- apps/web/src/app/decks/_components/columns/deck-faq-column.tsx[19-32]
- apps/web/src/app/perks/points/_components/points-basic-info.tsx[12-35]
- apps/web/src/app/perks/promote-post/_components/promote-post-intro.tsx[13-23]
- apps/web/src/features/ecency-center/sections/center-faq.tsx[14-41]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
✅ Compliance rules (platform): 84 rules
✅ Skills: 6 invoked
  add-feature
  add-query
  add-sdk-mutation
  add-test
  code-review
  debug
Review mode: 🧠 Deep: This is a substantial cross-cutting i18n/runtime loading change spanning webpack, SSR/client hydration, locale fallback behavior, multiple UI paths, and concurrency-like loading guards, with many independent defect opportunities.

Grey Divider

Tip of the day
💡 Did you know, you can tweak Display preferences with a live preview to see your comment before it ships

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread apps/web/src/features/i18n/use-faq-translations.ts
Comment thread apps/web/vitest.config.mts Outdated
Comment on lines +28 to +31
resolveId(this: any, id: string, importer: string | undefined) {
if (!id.endsWith('en-US.json?faq')) return null;
return this.resolve(id.replace('?faq', ''), importer, { skipSelf: true });
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

2. New any in vitest plugin 📘 Rule violation ⚙ Maintainability

apps/web/vitest.config.mts introduces any types (this: any and as any) in new code,
violating the ban on new any usage. This reduces type safety and can mask real typing issues in
the test/build tooling layer.
Agent Prompt
## Issue description
New code in `vitest.config.mts` introduces `any` (`this: any` and `faqQuery as any`), which violates the rule disallowing new `any`/implicit-any usage in changed TypeScript.

## Issue Context
The file comment notes plugin typing mismatches due to dual Vite versions; however, the code can still avoid `any` by using `unknown` plus a narrow cast for the minimal `this.resolve(...)` shape, or by using a compatible Vite `Plugin`/`PluginContext` type without falling back to `any`.

## Fix Focus Areas
- apps/web/vitest.config.mts[25-37]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Taken: the plugin is now typed from vitest's own UserConfig['plugins'] element type, so this.resolve is typed by the plugin context and the as any on it is gone (the pre-existing stubStyles cast is untouched).

Comment on lines +20 to +22
.then(() => {
if (!cancelled) setReady(true);
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

3. Stale load marks language ready 🐞 Bug ≡ Correctness

useFaqTranslations can set ready=true when an earlier locale load resolves even if
i18n.language has since changed and the current language is still loading. This can cause
consumers to render fallback/raw keys or stale-language FAQ content and may remain stuck if the
later “current language” completion tries to set ready to true again and React skips the no-op
update.
Agent Prompt
## Issue description
`useFaqTranslations` can incorrectly mark itself `ready` when an older locale’s FAQ resource load completes after i18next has already switched languages, so consumers may render/search FAQ strings for the new locale before its bundle is available (showing fallback English, raw keys, incorrect results, or stale-language content).

## Issue Context
The hook starts asynchronous loads per language in response to language changes, but it only cancels/guards on unmount and unconditionally sets `ready=true` on any completion without checking that the completion matches the active language (or the latest in-flight request). During rapid language switches, an earlier request can resolve later and flip readiness early; when the real current-language load finishes, calling `setReady(true)` again may be a no-op, potentially leaving the UI stuck until another render. Fix by associating each load with the language (or a monotonically increasing request generation) and only updating readiness when the resolved request still matches the active/latest language, recomputing readiness for the active language rather than unconditionally setting it to true.

## Fix Focus Areas
- apps/web/src/features/i18n/use-faq-translations.ts[15-32]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This was fixed in the previous push: readiness is re-probed against i18n.language at the moment a load settles (setReady(isFaqLoaded(i18n.language))), so a load that started for an earlier language cannot mark the current one ready, and the language-change handler cancels a pending retry and starts a fresh load; a later completion for the current language sets ready from its own probe, so there is no stuck no-op update.

@feruzm
feruzm force-pushed the perf/lazy-faq-locale branch from 4a2b50a to 9e460e7 Compare August 21, 2026 09:30
…ndle

en-US.json is the only eagerly bundled locale, and its FAQ articles
(static.faq.*-header / *-body: 262 keys, about 17 KB gzipped, a quarter
of the file) shipped in first-load JS on every route although only the
FAQ surfaces render them.

A webpack loader now splits that file at build time: the plain import
gets the locale without the articles and a ?faq import gets only them,
so the JSON on disk stays whole and Crowdin keeps one source file. The
other locales are already loaded on demand as whole files and need no
change. ensureFaqLoaded merges the English articles back into the
translation namespace (and loads them alongside any other language, since
English is the per-key fallback), so every existing
i18next.t("static.faq.…") call keeps working once it has resolved. The
FAQ and About pages await it on the server; the FAQ page hands the
English articles to its client components through <FaqResources> so they
hydrate with the strings the server rendered; the help-center search,
decks FAQ column and the two perks explainers render their FAQ strings
once useFaqTranslations reports them present. Only English is ever
primed from the server: the other locales belong to loadLocale as whole
files, whose guard now probes a core key so a partial bundle can never
pass for the full file. A vitest resolver maps the ?faq import to the
plain file in tests.

Closes #1598
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (4) 📘 Rule violations (1) 📎 Requirement gaps (1) 📜 Skill insights (1)

Grey Divider


Action required

1. useFaqTranslations missing effect deps 📜 Skill insight ≡ Correctness
Description
useFaqTranslations uses i18n, isFaqLoaded, and ensureFaqLoaded inside a useEffect but
provides an empty dependency array, which violates the hook-deps requirement and risks stale
closures if any referenced values change. This can lead to the FAQ readiness state not updating
correctly in future refactors or different runtime setups.
Code

apps/web/src/features/i18n/use-faq-translations.ts[33]

+  }, []);
Relevance

●●● Strong

Explicit hook-dependency compliance rule makes this a deterministic, low-risk fix typically
accepted.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 2668377 requires hook dependency arrays to include all referenced values. The new
useFaqTranslations hook registers an effect that references i18n, ensureFaqLoaded, and
isFaqLoaded, but the dependency array is [].

apps/web/src/features/i18n/use-faq-translations.ts[15-33]
Skill: code-review: Skill: code-review

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`useFaqTranslations()` references values inside `useEffect` while using an empty dependency array (`[]`), which violates the requirement that hook dependency arrays be complete.
## Issue Context
The effect references imported functions (`ensureFaqLoaded`, `isFaqLoaded`) and the `i18n` instance. Even if these are currently stable, the rule requires complete deps to prevent stale-closure bugs during future changes.
## Fix Focus Areas
- apps/web/src/features/i18n/use-faq-translations.ts[15-33]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. en-US locale still eagerly bundled 📎 Requirement gap ➹ Performance ⭐ New
Description
The PR keeps en-US.json (core keys) eagerly bundled and registered during i18next init, so it is
still loaded on every route rather than on demand. This does not meet the checklist requirement to
avoid statically bundling the default locale in first-load JS.
Code

apps/web/next.config.js[R261-264]

+    // en-US is the only eagerly bundled locale. Its FAQ articles (~17 KB gz, a
+    // quarter of the file) are only rendered by the FAQ surfaces, so the
+    // loader splits them out: the plain import gets the locale without them,
+    // `?faq` gets only them, loaded on demand by features/i18n/faq.ts (#1598).
Evidence
PR Compliance ID 1 requires the default locale not be included in initial JS bundles and instead be
lazy-loaded. The added webpack rule explicitly keeps en-US as the eagerly bundled locale (only
splitting FAQ articles), and i18next still registers en-US resources from a static
require("./locales/en-US.json"), meaning en-US is still eagerly loaded on every route.

Default locale (en-US.json) is not bundled eagerly on every route
apps/web/next.config.js[261-271]
apps/web/src/features/i18n/index.ts[85-87]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The default locale (`en-US.json`) is still bundled and registered eagerly, so it remains part of first-load JS across routes.

## Issue Context
This PR introduces a webpack loader to split out FAQ articles, but the checklist item requires the default locale itself to be loaded on demand rather than being present in the initial bundle.

## Fix Focus Areas
- apps/web/src/features/i18n/index.ts[85-177]
- apps/web/next.config.js[261-271]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Stale load marks ready 🐞 Bug ≡ Correctness ⭐ New
Description
useFaqTranslations sets ready=true when any in-flight ensureFaqLoaded call resolves without
verifying i18next is still on the language that started the request. During overlapping language
changes, an earlier load can mark readiness for a newer locale that is still loading, causing
consumers to render/filter FAQ content using fallback or stale translations (including raw
static.faq.* keys) and potentially preventing dependent effects from rerunning because ready is
already true.
Code

apps/web/src/features/i18n/use-faq-translations.ts[R19-21]

+      ensureFaqLoaded(i18n.language)
+        .then(() => {
+          if (!cancelled) setReady(true);
Evidence
The hook kicks off asynchronous FAQ loading for the current language but only guards completion
against component cancellation, not against subsequent language changes, so requests started for
prior locales remain active. Because resource loading is independently keyed by language and the
hook accepts every successful completion to flip a single shared ready boolean that consumers use
both for rendering translations and recomputing translated FAQ search results, an older request can
resolve after a newer locale becomes active and set ready before that newer locale finishes
loading, leading to fallback/incorrect rendering until the real load completes (and possibly no
further updates if ready was already true).

apps/web/src/features/i18n/use-faq-translations.ts[15-32]
apps/web/src/features/i18n/faq.ts[45-62]
apps/web/src/app/decks/_components/columns/deck-faq-column.tsx[19-32]
apps/web/src/features/ecency-center/sections/center-faq.tsx[22-41]
apps/web/src/features/i18n/use-faq-translations.ts[15-31]
apps/web/src/features/i18n/use-faq-translations.ts[8-10]
apps/web/src/app/perks/points/_components/points-basic-info.tsx[33-35]
apps/web/src/features/ecency-center/sections/center-faq.tsx[74-86]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`useFaqTranslations` publishes readiness from an asynchronous FAQ load even if the active language changed after that load started. As a result, a completion for a request initiated under an earlier language can incorrectly set `ready=true` for a newer locale that is still loading, causing consumers to render fallback/stale FAQ translations (including raw `static.faq.*` keys) and potentially skipping recomputation paths that depend on `ready` because it is already `true`.

## Issue Context
`load()`/`ensureFaqLoaded` is triggered on each `languageChanged` event and FAQ resource loading is asynchronous per language. Capture the language associated with each initiated load and only update readiness when the promise resolves if that language still matches the currently active i18next language; additionally ensure that completion of the current language load triggers consumer updates (rendering and FAQ-search effects) even if an older request already completed.

## Fix Focus Areas
- apps/web/src/features/i18n/use-faq-translations.ts[15-32]
- apps/web/src/features/i18n/use-faq-translations.ts[17-22]
- apps/web/src/features/i18n/faq.ts[45-62]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. Failed load hides content 🐞 Bug ☼ Reliability ⭐ New
Description
The hook suppresses every ensureFaqLoaded rejection and leaves ready false permanently until a
language change or remount. A transient FAQ chunk or locale load failure therefore removes the
explainer text and FAQ lists for the rest of the mounted session, with neither retry nor visible
error, whereas these strings were previously available from the eager English fallback.
Code

apps/web/src/features/i18n/use-faq-translations.ts[R23-25]

+        .catch(() => {
+          /* the strings stay hidden; nothing else to do */
+        });
Evidence
The only rejection handler is an empty catch, and loading is initiated only on mount and
language-change events. Every changed consumer conditionally omits its FAQ content while the hook
remains false, so a rejected request has a persistent visible impact.

apps/web/src/features/i18n/use-faq-translations.ts[15-33]
apps/web/src/app/perks/points/_components/points-basic-info.tsx[30-35]
apps/web/src/app/perks/promote-post/_components/promote-post-intro.tsx[19-23]
apps/web/src/features/ecency-center/sections/center-faq.tsx[71-89]
apps/web/src/app/decks/_components/columns/deck-faq-column.tsx[65-74]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
FAQ loading failures are silently converted into permanently hidden content. The hook neither retries nor exposes an error/fallback state, so transient chunk failures remove user-facing FAQ articles for the component's mounted lifetime.

## Issue Context
English is the per-key fallback and non-English loading already requests English alongside the active locale. Preserve usable fallback content where possible, and provide bounded retry or an explicit recoverable error state rather than swallowing the rejection indefinitely.

## Fix Focus Areas
- apps/web/src/features/i18n/use-faq-translations.ts[15-32]
- apps/web/src/features/i18n/faq.ts[45-62]
- apps/web/src/app/perks/points/_components/points-basic-info.tsx[30-35]
- apps/web/src/app/perks/promote-post/_components/promote-post-intro.tsx[19-23]
- apps/web/src/features/ecency-center/sections/center-faq.tsx[71-89]
- apps/web/src/app/decks/_components/columns/deck-faq-column.tsx[65-74]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View medium (2)
5. New any in vitest plugin 📘 Rule violation ⚙ Maintainability
Description
apps/web/vitest.config.mts introduces any types (this: any and as any) in new code,
violating the ban on new any usage. This reduces type safety and can mask real typing issues in
the test/build tooling layer.
Code

apps/web/vitest.config.mts[R28-31]

+  resolveId(this: any, id: string, importer: string | undefined) {
+    if (!id.endsWith('en-US.json?faq')) return null;
+    return this.resolve(id.replace('?faq', ''), importer, { skipSelf: true });
+  }
Relevance

●●● Strong

Team has accepted removing explicit any in new test/config code under no-any policy.

PR-#1572

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 2668119 forbids introducing any in new/modified TypeScript. The new Vite plugin
uses resolveId(this: any, ...) and is also inserted into the plugins list via an as any cast.

Rule 2668119: Disallow implicit and any types in new TypeScript code
apps/web/vitest.config.mts[25-37]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
New code in `vitest.config.mts` introduces `any` (`this: any` and `faqQuery as any`), which violates the rule disallowing new `any`/implicit-any usage in changed TypeScript.
## Issue Context
The file comment notes plugin typing mismatches due to dual Vite versions; however, the code can still avoid `any` by using `unknown` plus a narrow cast for the minimal `this.resolve(...)` shape, or by using a compatible Vite `Plugin`/`PluginContext` type without falling back to `any`.
## Fix Focus Areas
- apps/web/vitest.config.mts[25-37]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Stale load marks language ready 🐞 Bug ≡ Correctness
Description
useFaqTranslations can set ready=true when an earlier locale load resolves even if
i18n.language has since changed and the current language is still loading. This can cause
consumers to render fallback/raw keys or stale-language FAQ content and may remain stuck if the
later “current language” completion tries to set ready to true again and React skips the no-op
update.
Code

apps/web/src/features/i18n/use-faq-translations.ts[R20-22]

+        .then(() => {
+          if (!cancelled) setReady(true);
+        })
Relevance

●●● Strong

Recent accepted async state-lifecycle fixes show team accepts guarding stale async completions.

PR-#1579

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The hook kicks off asynchronous, per-language loads triggered by a mutable global i18next language
(e.g., via languageChanged), but the continuation after each promise resolves only guards against
component unmount and does not verify that the resolved load corresponds to the currently active
language (or latest request). Because resource loads complete independently, an older request can
finish after a language switch and still mark readiness true; consumers rely on this single boolean
gate to proceed with translating/searching using the current global language, so readiness from an
outdated load does not guarantee the active language’s FAQ bundle is actually present.

apps/web/src/features/i18n/use-faq-translations.ts[15-32]
apps/web/src/features/i18n/faq.ts[45-62]
apps/web/src/features/i18n/index.ts[165-168]
apps/web/src/app/perks/points/_components/points-basic-info.tsx[12-35]
apps/web/src/features/ecency-center/sections/center-faq.tsx[14-41]
apps/web/src/features/i18n/faq.ts[29-31]
apps/web/src/features/i18n/faq.ts[45-63]
apps/web/src/app/perks/points/_components/points-basic-info.tsx[33-35]
apps/web/src/app/decks/_components/columns/deck-faq-column.tsx[23-32]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`useFaqTranslations` can incorrectly mark itself `ready` when an older locale’s FAQ resource load completes after i18next has already switched languages, so consumers may render/search FAQ strings for the new locale before its bundle is available (showing fallback English, raw keys, incorrect results, or stale-language content).
## Issue Context
The hook starts asynchronous loads per language in response to language changes, but it only cancels/guards on unmount and unconditionally sets `ready=true` on any completion without checking that the completion matches the active language (or the latest in-flight request). During rapid language switches, an earlier request can resolve later and flip readiness early; when the real current-language load finishes, calling `setReady(true)` again may be a no-op, potentially leaving the UI stuck until another render. Fix by associating each load with the language (or a monotonically increasing request generation) and only updating readiness when the resolved request still matches the active/latest language, recomputing readiness for the active language rather than unconditionally setting it to true.
## Fix Focus Areas
- apps/web/src/features/i18n/use-faq-translations.ts[15-32]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

7. Failed loads stay hidden 🐞 Bug ☼ Reliability
Description
The hook suppresses every ensureFaqLoaded rejection without recording an error or scheduling a
retry, while all new consumers hide their FAQ content whenever ready remains false. A transient
chunk or locale request failure therefore leaves the affected FAQ list or explainer blank for the
component's lifetime, rather than recovering or showing an error.
Code

apps/web/src/features/i18n/use-faq-translations.ts[R23-25]

+        .catch(() => {
+          /* the strings stay hidden; nothing else to do */
+        });
Relevance

● Weak

Team recently rejected adding separate UI/error-state handling for failed async checks in similar
hook.

PR-#1528

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The catch block intentionally performs no state update, retry, or reporting. Each updated surface
gates article rendering on the hook's boolean, so a rejected load leaves those elements absent with
no recovery path until the effect is retriggered by remount or another language change.

apps/web/src/features/i18n/use-faq-translations.ts[15-33]
apps/web/src/app/decks/_components/columns/deck-faq-column.tsx[19-32]
apps/web/src/app/decks/_components/columns/deck-faq-column.tsx[65-73]
apps/web/src/app/perks/points/_components/points-basic-info.tsx[30-35]
apps/web/src/app/perks/promote-post/_components/promote-post-intro.tsx[19-23]
apps/web/src/features/ecency-center/sections/center-faq.tsx[71-87]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A rejected lazy FAQ load is swallowed and leaves FAQ content permanently hidden until unmount or another language change.
## Issue Context
Add a recoverable error/loading state or bounded retry path. Consumers should provide a retry/error fallback rather than silently rendering no article content indefinitely.
## Fix Focus Areas
- apps/web/src/features/i18n/use-faq-translations.ts[12-35]
- apps/web/src/app/decks/_components/columns/deck-faq-column.tsx[19-32]
- apps/web/src/app/perks/points/_components/points-basic-info.tsx[12-35]
- apps/web/src/app/perks/promote-post/_components/promote-post-intro.tsx[13-23]
- apps/web/src/features/ecency-center/sections/center-faq.tsx[14-41]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
Review mode: 🧠 Deep: This changes build-time loading, i18next fallback/guards, SSR hydration, locale switching, and multiple client FAQ surfaces across many independent paths, creating a dense set of easy-to-miss behavioral defects.

Grey Divider

Tip of the day
💡 Did you know, you can tweak Display preferences with a live preview to see your comment before it ships

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

@feruzm
feruzm force-pushed the perf/lazy-faq-locale branch from 9e460e7 to 44a9b62 Compare August 21, 2026 09:30
Comment thread apps/web/next.config.js
Comment on lines +19 to +21
ensureFaqLoaded(i18n.language)
.then(() => {
if (!cancelled) setReady(true);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

2. Stale load marks ready 🐞 Bug ≡ Correctness

useFaqTranslations sets ready=true when any in-flight ensureFaqLoaded call resolves without
verifying i18next is still on the language that started the request. During overlapping language
changes, an earlier load can mark readiness for a newer locale that is still loading, causing
consumers to render/filter FAQ content using fallback or stale translations (including raw
static.faq.* keys) and potentially preventing dependent effects from rerunning because ready is
already true.
Agent Prompt
## Issue description
`useFaqTranslations` publishes readiness from an asynchronous FAQ load even if the active language changed after that load started. As a result, a completion for a request initiated under an earlier language can incorrectly set `ready=true` for a newer locale that is still loading, causing consumers to render fallback/stale FAQ translations (including raw `static.faq.*` keys) and potentially skipping recomputation paths that depend on `ready` because it is already `true`.

## Issue Context
`load()`/`ensureFaqLoaded` is triggered on each `languageChanged` event and FAQ resource loading is asynchronous per language. Capture the language associated with each initiated load and only update readiness when the promise resolves if that language still matches the currently active i18next language; additionally ensure that completion of the current language load triggers consumer updates (rendering and FAQ-search effects) even if an older request already completed.

## Fix Focus Areas
- apps/web/src/features/i18n/use-faq-translations.ts[15-32]
- apps/web/src/features/i18n/use-faq-translations.ts[17-22]
- apps/web/src/features/i18n/faq.ts[45-62]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Already fixed in a later push (this comment is on the first commit): readiness is re-probed against i18n.language when a load settles, so a load started for an earlier language cannot mark the current one ready, and a language change cancels any pending retry and starts a fresh load.

Comment on lines +23 to +25
.catch(() => {
/* the strings stay hidden; nothing else to do */
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

3. Failed load hides content 🐞 Bug ☼ Reliability

The hook suppresses every ensureFaqLoaded rejection and leaves ready false permanently until a
language change or remount. A transient FAQ chunk or locale load failure therefore removes the
explainer text and FAQ lists for the rest of the mounted session, with neither retry nor visible
error, whereas these strings were previously available from the eager English fallback.
Agent Prompt
## Issue description
FAQ loading failures are silently converted into permanently hidden content. The hook neither retries nor exposes an error/fallback state, so transient chunk failures remove user-facing FAQ articles for the component's mounted lifetime.

## Issue Context
English is the per-key fallback and non-English loading already requests English alongside the active locale. Preserve usable fallback content where possible, and provide bounded retry or an explicit recoverable error state rather than swallowing the rejection indefinitely.

## Fix Focus Areas
- apps/web/src/features/i18n/use-faq-translations.ts[15-32]
- apps/web/src/features/i18n/faq.ts[45-62]
- apps/web/src/app/perks/points/_components/points-basic-info.tsx[30-35]
- apps/web/src/app/perks/promote-post/_components/promote-post-intro.tsx[19-23]
- apps/web/src/features/ecency-center/sections/center-faq.tsx[71-89]
- apps/web/src/app/decks/_components/columns/deck-faq-column.tsx[65-74]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Already fixed in a later push: a failed load is retried twice with a delay before the strings stay hidden. English fallback content cannot be shown instead, because the English articles are exactly what failed to load in that case; until they arrive the consumers render nothing rather than raw keys.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Your organization has used all 50 credits included in the free plan this billing period. To keep receiving reviews, upgrade your plan.

@feruzm
feruzm merged commit 1975e61 into develop Aug 21, 2026
8 checks passed
@feruzm
feruzm deleted the perf/lazy-faq-locale branch August 21, 2026 10:20
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.

en-US.json is eager on every route (~68 KB gz)

1 participant