Skip to content

fix(i18n): give the /home route a provider layout — it rendered raw keys - #16056

Open
jonlwheat2-gif wants to merge 1 commit into
diegosouzapw:release/v3.8.52from
jonlwheat2-gif:fix/i18n-home-route-missing-provider
Open

jonlwheat2-gif wants to merge 1 commit into
diegosouzapw:release/v3.8.52from
jonlwheat2-gif:fix/i18n-home-route-missing-provider

Conversation

@jonlwheat2-gif

@jonlwheat2-gif jonlwheat2-gif commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Problem

/home renders raw dotted keys instead of copy — home.quickStart, home.step1Title, kimiSponsorBanner.foundingFriendTitle, cheaperInferenceSponsorBanner.title, vscodeCopilotBanner.title — and the console fills with:

MISSING_MESSAGE: Could not resolve `home` in messages for locale `en`.
    at HomePageClient (...)
    at HomeProviderTopologySection (...)

Regression from #14835 (1709fb4), which split the client i18n catalog per route so each route serializes only the namespaces its own client components use. Nested NextIntlClientProviders replace rather than merge, so a route whose provider omits a namespace renders those keys literally even though en.json has them.

/home never got a provider at all. The generator emitted one layout per section under src/app/(dashboard)/dashboard/, but home/ is a sibling of dashboard/, not a section beneath it:

file namespaces served to /home
src/app/layout.tsx root (30)
src/app/(dashboard)/layout.tsx chrome (26) — replaces root
src/app/(dashboard)/home/ no layout — nothing here

src/app/(dashboard)/dashboard/layout.tsx did mount the home set, but dashboard/page.tsx only does redirect("/home"), so the one provider built for the home page was attached to a redirect stub handing off to a route that had none.

Everything chrome-served renders correctly (Home, Welcome to OmniRoute, Quick nav, EN) — the failure boundary is exactly the namespaces outside chrome.

Fix

  • analyzer — compute home from the /home route subtree instead of the sibling dashboard/ directory. Walking home/ follows the page's imports into dashboard/, so the set is now exactly what the route renders. It also picks up usage, which the old derivation missed.
  • generator — emit (dashboard)/home/layout.tsx alongside the section layouts. A route that is not under dashboard/ now gets a provider for the same reason every section does.
  • remove (dashboard)/dashboard/layout.tsx. It mounted the home set across the whole dashboard subtree, but dashboard/page.tsx only redirects and every section overrides it — it shipped ~6 home-only namespaces into each section's payload for nothing.
  • regenerate routeNamespaces.generated.json. This also picks up codexKeyServiceMode; the freshness test was already red on it before this change.

Why no test caught it

tests/unit/i18n-route-namespaces.test.ts only iterates sections, and home is not a section — the gap was outside the test's entire view.

New tests/unit/i18n-route-provider-coverage.test.ts enumerates every route the analyzer emits a set for (home + sections), asserting each has a provider layout mounting that exact set, and that the set covers the namespaces its client components use. Namespace lists come from the real import closure, so a component that starts using a new namespace is caught automatically.

New tests/unit/i18n-home-route-namespaces.test.ts asserts the /home closure is delivered and that the exact keys from the report resolve to real copy.

Verification

  • tests/unit/i18n-route-namespaces.test.ts + i18n-route-provider-coverage + i18n-home-route-namespaces — 13/13 pass (the first was failing on the base branch before this change).
  • Generator is idempotent: a second run reports home route layout: pre-existing (kept).
  • npm run typecheck:core clean; npm run check:file-size OK; lint clean.
  • Pre-commit gates green: lint-staged, docs-sync, any-budget, tracked-artifacts, ai-attribution.

Pre-existing failures, unrelated — the wider i18n-* sweep is 588 pass / 6 fail. All 6 are in i18n-glossary-consistency-check and i18n-locale-scaffold (zh-CN glossary terms, README flag link, docs/guides/I18N.md row, mirror stub). They import check-glossary-consistency.mjs, glossary-normalize.mjs and locale-scaffold.mjs — none touched here.

Note on a seam that was considered and dropped

Rendering the route through a real NextIntlClientProvider does not work in this test harness: next-intl resolves messages from the app's request config, so the provider's messages prop is ignored (a sentinel passed as messages never reached the output). Such a test would assert nothing. Provider scoping is covered statically instead.

Fixes #16217

The per-route catalog split in diegosouzapw#14835 gives each dashboard route only the
namespaces its own client components use, via nested NextIntlClientProvider.
Nested providers REPLACE rather than merge, so a route whose provider omits
a namespace renders those keys as literal dotted paths.

/home never got a provider. The generator emitted a layout for every section
under src/app/(dashboard)/dashboard/, but home/ is a SIBLING of dashboard/ and
not a section beneath it, so it was invisible to both the generator and the
freshness test. /home therefore inherited only the chrome set from
(dashboard)/layout.tsx and rendered home.*, providers.*, kimiSponsorBanner.*,
cheaperInferenceSponsorBanner.* and vscodeCopilotBanner.* raw — the
MISSING_MESSAGE errors in the console, and the raw keys on screen.

Changes:

- analyzer: compute `home` from the /home route subtree instead of the
  sibling dashboard/ directory. Walking home/ follows the page's imports into
  dashboard/, so the set is now exactly what the route renders — it also picks
  up `usage`, which the old derivation missed.
- generator: emit (dashboard)/home/layout.tsx alongside the section layouts.
  A route that is not under dashboard/ now gets a provider for the same
  reason every section does.
- remove (dashboard)/dashboard/layout.tsx. It mounted the `home` set for the
  whole dashboard subtree, but dashboard/page.tsx only redirects to /home and
  every section overrides it — it shipped ~6 home-only namespaces into each
  section's payload for nothing.
- regenerate routeNamespaces.generated.json, which also picks up
  codexKeyServiceMode; the freshness test was already red on it.

Tests: tests/unit/i18n-home-route-namespaces.test.ts (namespaces used under
/home are delivered; the reported keys resolve to real copy) and
tests/unit/i18n-route-provider-coverage.test.ts (every route the analyzer
emits a set for has a provider layout mounting that set, and the set covers
its client components). The pre-existing gate only iterated `sections`, which
is why diegosouzapw#14835 shipped this.
@jonlwheat2-gif

Copy link
Copy Markdown
Contributor Author

Verified in a browser on this branch (dev server, release/v3.8.52 base). The fix works — /home renders real copy, and there are zero MISSING_MESSAGE: Could not resolve 'home' errors in the console.

One thing worth separating out so it doesn't get attributed to this PR: /endpoint logs ~120 MISSING_MESSAGE errors for endpoint.cloudUploadDestination / cloudUploadWarning / cloudUploadConsent. Those three keys are missing from en.json itself (never added to any catalog), so they fail identically on the base branch — pre-existing, not a regression here. The UI is unaffected because translateOrFallback (EndpointPageClient.tsx:184) passes a hardcoded English fallback; the error is emitted inside t() before that guard can catch it. Filed separately as #16220.

Also confirmed: deleting (dashboard)/dashboard/layout.tsx is safe — every page.tsx under (dashboard) still resolves to a layout ancestor, and nothing imports the deleted file.

@diegosouzapw

Copy link
Copy Markdown
Owner

Hi @jonlwheat2-gif — closing this pull request to clear the open queue. The branch fix/i18n-home-route-missing-provider is kept, so the commits stay. Comment here if it should be reopened.

@diegosouzapw

Copy link
Copy Markdown
Owner

Hi @jonlwheat2-gif — reopening this pull request. The close was a mistake, and it is back in the merge queue. The branch fix/i18n-home-route-missing-provider was kept.

@diegosouzapw diegosouzapw reopened this Oct 10, 2026

This branch has not been deployed

No deployments
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.

[BUG] /home renders raw i18n keys (home.*, *SponsorBanner.*) — route has no provider layout

2 participants