fix(i18n): hydrate in the SSR locale instead of regressing to en

The client's message cache held only en at hydration, so a non-en user
rendered English until the locale's catalog chunk loaded — and stayed
English if that import failed or lost a race. __root beforeLoad now
dehydrates the active locale's compiled catalog with the page and the
provider primes its cache from that payload, so first paint is already in
the user's locale with no dependency on a client-side chunk load.
ensureMessages dedupes concurrent loads and drops rejected ones so a
retry can succeed, and the provider's fallback loader retries instead of
silently stranding the UI in en.
This commit is contained in:
2026-08-23 19:47:31 -07:00
parent 11bb70316a
commit 792425a83e
3 changed files with 72 additions and 14 deletions
+21 -3
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();
messageCache[locale] = messages;
return messages;
// 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]!;
}