Repository navigation
fix(marketing): serve the request's language, and make regressing this a blocked deploy #6991
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,47 @@ | ||
| // Re-exported so existing imports keep working; the implementation and the | ||
| // explanation of why RSC needs it live in @superset/i18n/server. | ||
| export { initServerI18n } from "@superset/i18n/server"; | ||
| import { resolveLocale, type SupportedLocale } from "@superset/i18n"; | ||
| import { | ||
| initServerI18n as activateServerI18n, | ||
| preloadServerLocale, | ||
| } from "@superset/i18n/server"; | ||
| import { headers } from "next/headers"; | ||
|
|
||
| // "ja,en-US;q=0.9,en;q=0.8" -> ["ja", "en-US", "en"], ordered by quality. | ||
| function parseAcceptLanguage(header: string | null): string[] { | ||
| if (!header) return []; | ||
| return header | ||
| .split(",") | ||
| .map((part) => { | ||
| const [tag, ...params] = part.trim().split(";"); | ||
| const q = params | ||
| .map((p) => p.trim()) | ||
| .find((p) => p.startsWith("q=")) | ||
| ?.slice(2); | ||
| const quality = q ? Number.parseFloat(q) : 1; | ||
| return { tag: (tag ?? "").trim(), quality }; | ||
| }) | ||
| .filter((e) => e.tag && e.tag !== "*" && Number.isFinite(e.quality)) | ||
| .sort((a, b) => b.quality - a.quality) | ||
| .map((e) => e.tag); | ||
| } | ||
|
|
||
| /** | ||
| * Activates i18n for a React Server Components render, in the language the | ||
| * request asked for. | ||
| * | ||
| * A bare seeding call activates the default locale, which renders the whole | ||
| * RSC pass in English no matter what Accept-Language says — client components | ||
| * then re-localize on hydration, so visitors saw English heroes above | ||
| * localized bodies plus a hydration mismatch on every such page. Every route | ||
| * entry must await this (the layout is pruned on client-side navigation, so | ||
| * seeding there covers only full document loads — see @superset/i18n/server). | ||
| * | ||
| * These pages are already dynamically rendered (the nav resolves the session | ||
| * per request), so reading headers() adds no new rendering cost. | ||
| */ | ||
| export async function initServerI18n(): Promise<SupportedLocale> { | ||
| const acceptLanguage = (await headers()).get("accept-language"); | ||
| const locale = resolveLocale(parseAcceptLanguage(acceptLanguage)); | ||
| await preloadServerLocale(locale); | ||
| activateServerI18n(locale); | ||
| return locale; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,7 +17,11 @@ const ROUTE_ENTRY = /(?:^|\/)(?:page|not-found)\.tsx$/; | |
| // Anchored to the start of a line so a mention inside a comment or a string | ||
| // literal cannot satisfy the check; paired with the import so the call has to | ||
| // resolve to the real helper. | ||
| const SEEDS = /^\s*initServerI18n\(\);/m; | ||
| // The call must be awaited: a bare initServerI18n() activates the default | ||
| // locale and served every visitor English heroes above localized bodies — | ||
| // request-locale resolution is async, so an un-awaited call cannot have | ||
| // worked. (Regression guard for the 2026-08-29 production finding.) | ||
| const SEEDS = /^\s*(?:const\s+\w+\s*=\s*)?await initServerI18n\(\);/m; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
rg -n -C 8 '\bSEEDS\b|initServerI18n\s*\(' \
packages/i18n/test/rsc-seeding.test.ts \
apps/marketing/src --glob '*.tsx'Repository: superset-sh/superset Length of output: 44040 Reject mixed awaited and unawaited 🤖 Prompt for AI Agents |
||
| const IMPORTS = /^import\s*\{[^}]*\binitServerI18n\b[^}]*\}\s*from\s*["']/m; | ||
| const USES_I18N = /<Trans\b|useLingui\(/; | ||
| const CLIENT_COMPONENT = /^\s*["']use client["']/m; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Reject invalid or zero-quality language entries.
Number.parseFloataccepts prefixes such as0.5junk, and the filter keeps finite values outside the valid q-value range, including0. A request such asfr;q=0,ja;q=0can therefore selectfrinstead of falling back, andja;q=2can outrank valid preferences. Parse a strict q-value and keep only0 < q <= 1.🤖 Prompt for AI Agents