Repository navigation
fix(i18n): give the /home route a provider layout — it rendered raw keys - #16056
jonlwheat2-gif wants to merge 1 commit into
Conversation
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.
|
Verified in a browser on this branch (dev server, One thing worth separating out so it doesn't get attributed to this PR: Also confirmed: deleting |
|
Hi @jonlwheat2-gif — closing this pull request to clear the open queue. The branch |
|
Hi @jonlwheat2-gif — reopening this pull request. The close was a mistake, and it is back in the merge queue. The branch |
Problem
/homerenders raw dotted keys instead of copy —home.quickStart,home.step1Title,kimiSponsorBanner.foundingFriendTitle,cheaperInferenceSponsorBanner.title,vscodeCopilotBanner.title— and the console fills with:Regression from #14835 (
1709fb4), which split the client i18n catalog per route so each route serializes only the namespaces its own client components use. NestedNextIntlClientProviders replace rather than merge, so a route whose provider omits a namespace renders those keys literally even thoughen.jsonhas them./homenever got a provider at all. The generator emitted one layout per section undersrc/app/(dashboard)/dashboard/, buthome/is a sibling ofdashboard/, not a section beneath it:/homesrc/app/layout.tsxroot(30)src/app/(dashboard)/layout.tsxchrome(26) — replaces rootsrc/app/(dashboard)/home/src/app/(dashboard)/dashboard/layout.tsxdid mount thehomeset, butdashboard/page.tsxonly doesredirect("/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
homefrom the/homeroute subtree instead of the siblingdashboard/directory. Walkinghome/follows the page's imports intodashboard/, so the set is now exactly what the route renders. It also picks upusage, which the old derivation missed.(dashboard)/home/layout.tsxalongside the section layouts. A route that is not underdashboard/now gets a provider for the same reason every section does.(dashboard)/dashboard/layout.tsx. It mounted thehomeset across the whole dashboard subtree, butdashboard/page.tsxonly redirects and every section overrides it — it shipped ~6 home-only namespaces into each section's payload for nothing.routeNamespaces.generated.json. This also picks upcodexKeyServiceMode; the freshness test was already red on it before this change.Why no test caught it
tests/unit/i18n-route-namespaces.test.tsonly iteratessections, andhomeis not a section — the gap was outside the test's entire view.New
tests/unit/i18n-route-provider-coverage.test.tsenumerates 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.tsasserts the/homeclosure 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).home route layout: pre-existing (kept).npm run typecheck:coreclean;npm run check:file-sizeOK; lint clean.Pre-existing failures, unrelated — the wider
i18n-*sweep is 588 pass / 6 fail. All 6 are ini18n-glossary-consistency-checkandi18n-locale-scaffold(zh-CN glossary terms, README flag link,docs/guides/I18N.mdrow, mirror stub). They importcheck-glossary-consistency.mjs,glossary-normalize.mjsandlocale-scaffold.mjs— none touched here.Note on a seam that was considered and dropped
Rendering the route through a real
NextIntlClientProviderdoes not work in this test harness: next-intl resolves messages from the app's request config, so the provider'smessagesprop is ignored (a sentinel passed asmessagesnever reached the output). Such a test would assert nothing. Provider scoping is covered statically instead.Fixes #16217