fix(marketing): serve the request's language, and make regressing this a blocked deploy

Merged
KI
2mo ago
+147-54

The failure mode

A production page review (152 screenshots, en/ja/ru/tr) found 25 marketing pages serving English heroes or whole English bodies to non-English visitors, each with a React #418 hydration mismatch. Root cause: every route entry called bare initServerI18n(), which activates the default locale regardless of Accept-Language — the entire RSC pass rendered English, and client components re-localized on hydration (masking it on some pages, mismatching on all). Sentry saw none of it: recoverable hydration errors aren't captured, so this class was invisible to monitoring, and status-code smoke tests can't see it either.

Point fix

initServerI18n() in marketing now resolves Accept-Language per request (quality-ordered), preloads that catalog, activates it, and returns the locale; all 31 route entries await it; the layout writes the resolved locale into <html lang> (was hardcoded "en"). Pages are already dynamic (session-aware nav), so no rendering-cost change.

Runtime proof (local build): ja request → lang="ja", 1,501 CJK chars, hero どのチームにも分かりやすい料金; en request unchanged; only remaining English is by-design SEO <meta description>.

Systematic guards — the "this must never ship again" part

  1. Build-time: the RSC seeding ratchet now requires the awaited request-aware call. A bare or un-awaited initServerI18n() — the exact bug — fails bun test packages/i18n. Proven by reverting one call site: 3 pass / 1 fail naming the file.
  2. Deploy-time: smoke-routes.ts --localized (wired into the marketing deploy step) fetches every route with Accept-Language: ja and fails on served lang≠"ja" or a page with no Japanese text — both read the raw served HTML, so client-side patch-up cannot mask a server regression. Run against production today it fails on every page, including ones the browser review had passed.

Follow-ups noted, not in this PR

  • Capture React recoverable/hydration errors into Sentry (the monitoring blind spot)
  • app.superset.sh/cli/auth/code renders its error string pre-catalog-load (client-side timing, all locales)
  • CSP on app.superset.sh blocks the Cloudflare Insights beacon on every page

https://claude.ai/code/session_012U97YWVsDvZwQkBaSGEmor


Summary by cubic

Marketing pages now render in the request's language instead of always English. The old initServerI18n() activated the default locale regardless of Accept-Language, so 25 pages sent English heroes or whole English bodies to ja/ru/tr visitors, and every one produced a React hydration mismatch that Sentry never captured because recoverable hydration errors aren't reported.

Bug Fixes

  • The marketing initServerI18n() now reads Accept-Language per request, preloads and activates that catalog, and returns the locale; all 31 route entries await it.
  • The layout writes the resolved locale into <html lang> instead of hardcoded "en".
  • No rendering-cost change — pages are already dynamically rendered for the session-aware nav, and the only remaining English in a ja page is the by-design SEO meta description.

Regression Guards

  • The RSC seeding test now rejects a bare or un-awaited initServerI18n() call; reverting one call site fails the build.
  • smoke-routes.ts --localized, wired into the marketing deploy step, fetches every route with Accept-Language: ja and fails when the served HTML has lang="en" or no Japanese text, so client-side patch-up can't mask a server regression.

Summary by CodeRabbit

  • New Features

    • Marketing pages now serve content in the language requested by the browser.
    • The page language attribute reflects the selected locale.
  • Bug Fixes

    • Improved localized rendering by ensuring translations load before page content appears.
    • Enhanced localization consistency across marketing, blog, legal, marketplace, and 404 pages.
  • Tests

    • Added smoke-test coverage to verify Japanese localization and translated page content across routes.