Fix landing locale toggle cache path
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import { InstitutionalLandingPage } from "@/components/landing/InstitutionalLandingPage";
|
import { InstitutionalLandingPage } from "@/components/landing/InstitutionalLandingPage";
|
||||||
|
import { LANDING_LOCALE_QUERY_PARAM } from "@/components/landing/landingLocale";
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "PolyWeather | Institutional Weather Signal Intelligence",
|
title: "PolyWeather | Institutional Weather Signal Intelligence",
|
||||||
@@ -32,6 +33,8 @@ export default async function HomePage({
|
|||||||
const qs = usp.toString();
|
const qs = usp.toString();
|
||||||
redirect(`/auth/callback${qs ? `?${qs}` : ""}`);
|
redirect(`/auth/callback${qs ? `?${qs}` : ""}`);
|
||||||
}
|
}
|
||||||
|
const rawLandingLocale = params[LANDING_LOCALE_QUERY_PARAM];
|
||||||
|
const landingLocale = Array.isArray(rawLandingLocale) ? rawLandingLocale[0] : rawLandingLocale;
|
||||||
const jsonLd = {
|
const jsonLd = {
|
||||||
"@context": "https://schema.org",
|
"@context": "https://schema.org",
|
||||||
"@type": "WebApplication",
|
"@type": "WebApplication",
|
||||||
@@ -68,7 +71,7 @@ export default async function HomePage({
|
|||||||
type="application/ld+json"
|
type="application/ld+json"
|
||||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||||
/>
|
/>
|
||||||
<InstitutionalLandingPage />
|
<InstitutionalLandingPage queryLocale={landingLocale} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -227,9 +227,10 @@ function WeatherWorkflowIllustration() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function resolveLandingLocale(): Promise<LandingLocale> {
|
async function resolveLandingLocale(queryLocale?: string | null): Promise<LandingLocale> {
|
||||||
const [cookieStore, headerStore] = await Promise.all([cookies(), headers()]);
|
const [cookieStore, headerStore] = await Promise.all([cookies(), headers()]);
|
||||||
return pickLandingLocale(
|
return pickLandingLocale(
|
||||||
|
queryLocale,
|
||||||
cookieStore.get(LANDING_LOCALE_COOKIE)?.value,
|
cookieStore.get(LANDING_LOCALE_COOKIE)?.value,
|
||||||
headerStore.get("accept-language"),
|
headerStore.get("accept-language"),
|
||||||
);
|
);
|
||||||
@@ -829,7 +830,11 @@ function InstitutionalLandingScreen({ locale }: { locale: LandingLocale }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function InstitutionalLandingPage() {
|
export async function InstitutionalLandingPage({
|
||||||
const locale = await resolveLandingLocale();
|
queryLocale,
|
||||||
|
}: {
|
||||||
|
queryLocale?: string | null;
|
||||||
|
} = {}) {
|
||||||
|
const locale = await resolveLandingLocale(queryLocale);
|
||||||
return <InstitutionalLandingScreen locale={locale} />;
|
return <InstitutionalLandingScreen locale={locale} />;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useTransition } from "react";
|
|
||||||
import { useRouter } from "next/navigation";
|
|
||||||
import {
|
import {
|
||||||
LANDING_LOCALE_COOKIE,
|
LANDING_LOCALE_COOKIE,
|
||||||
|
LANDING_LOCALE_QUERY_PARAM,
|
||||||
nextLandingLocale,
|
nextLandingLocale,
|
||||||
type LandingLocale,
|
type LandingLocale,
|
||||||
} from "@/components/landing/landingLocale";
|
} from "@/components/landing/landingLocale";
|
||||||
@@ -11,8 +10,6 @@ import {
|
|||||||
const ONE_YEAR_SECONDS = 60 * 60 * 24 * 365;
|
const ONE_YEAR_SECONDS = 60 * 60 * 24 * 365;
|
||||||
|
|
||||||
export function LandingLocaleToggle({ locale }: { locale: LandingLocale }) {
|
export function LandingLocaleToggle({ locale }: { locale: LandingLocale }) {
|
||||||
const router = useRouter();
|
|
||||||
const [isPending, startTransition] = useTransition();
|
|
||||||
const isEn = locale === "en-US";
|
const isEn = locale === "en-US";
|
||||||
|
|
||||||
const toggleLocale = () => {
|
const toggleLocale = () => {
|
||||||
@@ -24,16 +21,16 @@ export function LandingLocaleToggle({ locale }: { locale: LandingLocale }) {
|
|||||||
} catch {
|
} catch {
|
||||||
// Locale persistence is best-effort; the cookie is enough for SSR.
|
// Locale persistence is best-effort; the cookie is enough for SSR.
|
||||||
}
|
}
|
||||||
startTransition(() => {
|
|
||||||
router.refresh();
|
const url = new URL(window.location.href);
|
||||||
});
|
url.searchParams.set(LANDING_LOCALE_QUERY_PARAM, nextLocale);
|
||||||
|
window.location.assign(url.toString());
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={toggleLocale}
|
onClick={toggleLocale}
|
||||||
disabled={isPending}
|
|
||||||
className="inline-flex h-9 cursor-pointer items-center gap-1 rounded-md border border-slate-200 bg-white px-1 text-xs font-semibold text-slate-500 shadow-sm hover:border-slate-300 disabled:cursor-wait disabled:opacity-70"
|
className="inline-flex h-9 cursor-pointer items-center gap-1 rounded-md border border-slate-200 bg-white px-1 text-xs font-semibold text-slate-500 shadow-sm hover:border-slate-300 disabled:cursor-wait disabled:opacity-70"
|
||||||
aria-label={isEn ? "Switch language" : "切换语言"}
|
aria-label={isEn ? "Switch language" : "切换语言"}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -23,6 +23,10 @@ export function runTests() {
|
|||||||
path.join(root, "components", "landing", "LandingLocaleToggle.tsx"),
|
path.join(root, "components", "landing", "LandingLocaleToggle.tsx"),
|
||||||
"utf8",
|
"utf8",
|
||||||
);
|
);
|
||||||
|
const localeHelperSource = fs.readFileSync(
|
||||||
|
path.join(root, "components", "landing", "landingLocale.ts"),
|
||||||
|
"utf8",
|
||||||
|
);
|
||||||
const staticCitiesSource = fs.readFileSync(
|
const staticCitiesSource = fs.readFileSync(
|
||||||
path.join(root, "lib", "static-cities.ts"),
|
path.join(root, "lib", "static-cities.ts"),
|
||||||
"utf8",
|
"utf8",
|
||||||
@@ -48,6 +52,18 @@ export function runTests() {
|
|||||||
assert(!authActionsSource.includes("lucide-react"), "auth island must avoid shipping lucide-react");
|
assert(!authActionsSource.includes("lucide-react"), "auth island must avoid shipping lucide-react");
|
||||||
assert(!localeToggleSource.includes("lucide-react"), "locale island must avoid shipping lucide-react");
|
assert(!localeToggleSource.includes("lucide-react"), "locale island must avoid shipping lucide-react");
|
||||||
assert(!analyticsSource.includes("lucide-react"), "analytics island must avoid shipping lucide-react");
|
assert(!analyticsSource.includes("lucide-react"), "analytics island must avoid shipping lucide-react");
|
||||||
|
assert(
|
||||||
|
localeToggleSource.includes("new URL(window.location.href)") &&
|
||||||
|
localeToggleSource.includes("LANDING_LOCALE_QUERY_PARAM") &&
|
||||||
|
localeToggleSource.includes("window.location.assign(url.toString())"),
|
||||||
|
"landing locale toggle must navigate with an explicit locale query so CDN-cached HTML cannot ignore cookie-only changes",
|
||||||
|
);
|
||||||
|
assert(
|
||||||
|
localeHelperSource.includes("queryLocale") &&
|
||||||
|
source.includes("resolveLandingLocale(queryLocale") &&
|
||||||
|
appPageSource.includes("<InstitutionalLandingPage queryLocale={landingLocale} />"),
|
||||||
|
"landing route must prefer an explicit locale query before cookie or Accept-Language",
|
||||||
|
);
|
||||||
assert(source.includes("3 天免费试用"), "landing page must advertise the 3-day trial");
|
assert(source.includes("3 天免费试用"), "landing page must advertise the 3-day trial");
|
||||||
assert(source.includes("试用期权益和 Pro 一致,除了不显示付费 Telegram 群链接"), "landing page must state trial access matches Pro except the paid group link");
|
assert(source.includes("试用期权益和 Pro 一致,除了不显示付费 Telegram 群链接"), "landing page must state trial access matches Pro except the paid group link");
|
||||||
assert(!source.includes("高频刷新与 API 仍为 Pro 权益"), "landing page must not incorrectly exclude high-frequency refresh or API from trial access");
|
assert(!source.includes("高频刷新与 API 仍为 Pro 权益"), "landing page must not incorrectly exclude high-frequency refresh or API from trial access");
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
export type LandingLocale = "zh-CN" | "en-US";
|
export type LandingLocale = "zh-CN" | "en-US";
|
||||||
|
|
||||||
export const LANDING_LOCALE_COOKIE = "polyweather.locale";
|
export const LANDING_LOCALE_COOKIE = "polyweather.locale";
|
||||||
|
export const LANDING_LOCALE_QUERY_PARAM = "locale";
|
||||||
export const DEFAULT_LANDING_LOCALE: LandingLocale = "zh-CN";
|
export const DEFAULT_LANDING_LOCALE: LandingLocale = "zh-CN";
|
||||||
|
|
||||||
export function normalizeLandingLocale(value: string | null | undefined): LandingLocale | null {
|
export function normalizeLandingLocale(value: string | null | undefined): LandingLocale | null {
|
||||||
@@ -16,9 +17,13 @@ export function normalizeLandingLocale(value: string | null | undefined): Landin
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function pickLandingLocale(
|
export function pickLandingLocale(
|
||||||
|
queryLocale: string | null | undefined,
|
||||||
cookieLocale: string | null | undefined,
|
cookieLocale: string | null | undefined,
|
||||||
acceptLanguage: string | null | undefined,
|
acceptLanguage: string | null | undefined,
|
||||||
): LandingLocale {
|
): LandingLocale {
|
||||||
|
const fromQuery = normalizeLandingLocale(queryLocale);
|
||||||
|
if (fromQuery) return fromQuery;
|
||||||
|
|
||||||
const fromCookie = normalizeLandingLocale(cookieLocale);
|
const fromCookie = normalizeLandingLocale(cookieLocale);
|
||||||
if (fromCookie) return fromCookie;
|
if (fromCookie) return fromCookie;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user