Adding: themes, predictions, match reporting, ui fixes, sse fixes, badge fixes, match flow, i18n, match edits, perf, auth fix #22

Merged
kyle merged 43 commits from development into main 2026-08-23 23:09:01 -05:00
3 changed files with 72 additions and 14 deletions
Showing only changes of commit 792425a83e - Show all commits
+17 -2
View File
@@ -18,7 +18,13 @@ import { ColorSchemeScript, mantineHtmlProps } from "@mantine/core";
import { HeaderConfig } from "@/features/core/types/header-config";
import { playerQueries } from "@/features/players/queries";
import { ensureServerQueryData } from "@/lib/tanstack-query/utils/ensure";
import { DEFAULT_LOCALE, ensureMessages, resolveLocale } from "@/lib/i18n";
import {
DEFAULT_LOCALE,
ensureMessages,
getCachedMessages,
i18nMessagesQueryKey,
resolveLocale,
} from "@/lib/i18n";
import { getRootMeta } from "@/lib/i18n/meta";
import FullScreenLoader from "@/components/full-screen-loader";
import { CHROME_COLORS } from "@/lib/mantine/theme-colors";
@@ -132,7 +138,16 @@ export const Route = createRootRouteWithContext<{
context.queryClient,
playerQueries.auth()
);
await ensureMessages(resolveLocale(auth?.metadata?.locale));
const locale = resolveLocale(auth?.metadata?.locale);
await ensureMessages(locale);
// Dehydrate the catalog with the page so the client hydrates in the
// active locale instead of falling back to en until a chunk loads.
if (typeof window === "undefined" && locale !== DEFAULT_LOCALE) {
context.queryClient.setQueryData(
i18nMessagesQueryKey(locale),
getCachedMessages(locale)
);
}
return { auth };
} catch (error: any) {
if (isRedirect(error) || error instanceof Response) throw error;
+19 -1
View File
@@ -32,6 +32,8 @@ export function resolveLocale(candidate: string | null | undefined): AppLocale {
: DEFAULT_LOCALE;
}
const inflightLoads: Partial<Record<AppLocale, Promise<Messages>>> = {};
export async function ensureMessages(locale: AppLocale): Promise<Messages> {
const cached = messageCache[locale];
if (cached) return cached;
@@ -39,11 +41,27 @@ export async function ensureMessages(locale: AppLocale): Promise<Messages> {
const loader = catalogLoaders[`/src/locales/${locale}/messages.po`];
if (!loader) return messageCache[DEFAULT_LOCALE]!;
const { messages } = await loader();
// Dedupe concurrent loads; a rejected load is dropped so a retry can succeed.
const pending = (inflightLoads[locale] ??= loader()
.then(({ messages }) => {
messageCache[locale] = messages;
return messages;
})
.finally(() => {
delete inflightLoads[locale];
}));
return pending;
}
export function primeMessages(locale: AppLocale, messages: Messages) {
messageCache[locale] ??= messages;
}
// Seeded into the SSR-dehydrated query cache so hydration renders the active
// locale without depending on a client-side catalog chunk load.
export const i18nMessagesQueryKey = (locale: AppLocale) =>
["i18n-messages", locale] as const;
export function getCachedMessages(locale: AppLocale): Messages {
return messageCache[locale] ?? messageCache[DEFAULT_LOCALE]!;
}
+33 -8
View File
@@ -1,11 +1,14 @@
import { useEffect, useMemo, useState } from "react";
import { useQuery } from "@tanstack/react-query";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { I18nProvider } from "@lingui/react";
import type { Messages } from "@lingui/core";
import { playerQueries } from "@/features/players/queries";
import {
createI18n,
ensureMessages,
hasCachedMessages,
i18nMessagesQueryKey,
primeMessages,
resolveLocale,
} from "@/lib/i18n";
@@ -29,27 +32,49 @@ export const LinguiProvider = ({ children }: { children: React.ReactNode }) => {
refetchOnWindowFocus: false,
});
const queryClient = useQueryClient();
const locale = resolveLocale(data?.metadata?.locale);
const [catalogVersion, setCatalogVersion] = useState(0);
// Fresh instance per locale (and per SSR request tree). __root beforeLoad
// preloads the catalog on authed routes; on public routes (login) it may
// not be cached yet — createI18n falls back to en until the effect below
// loads it and bumps catalogVersion.
// dehydrates the active locale's catalog with the page, so hydration primes
// it from the query cache and never regresses to en for the SSR'd locale.
// Public routes (login) may still lack it — createI18n falls back to en
// until the effect below loads it and bumps catalogVersion.
const i18n = useMemo(
() => createI18n(locale),
() => {
if (!hasCachedMessages(locale)) {
const seeded = queryClient.getQueryData<Messages>(
i18nMessagesQueryKey(locale)
);
if (seeded) primeMessages(locale, seeded);
}
return createI18n(locale);
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[locale, catalogVersion]
[locale, catalogVersion, queryClient]
);
useEffect(() => {
if (hasCachedMessages(locale)) return;
let cancelled = false;
ensureMessages(locale).then(() => {
let retryTimer: ReturnType<typeof setTimeout> | undefined;
// A failed chunk load must not strand the UI in en — retry until the
// catalog arrives or the locale changes.
const load = () => {
ensureMessages(locale).then(
() => {
if (!cancelled) setCatalogVersion((v) => v + 1);
});
},
() => {
if (!cancelled) retryTimer = setTimeout(load, 3000);
}
);
};
load();
return () => {
cancelled = true;
if (retryTimer) clearTimeout(retryTimer);
};
}, [locale]);