fix(i18n): eliminate FOUC by loading messages server-side
Problem: Translation keys flashed for ~500ms before being replaced with actual translations due to client-side async message loading. Solution: - Load messages server-side in layout.tsx before initial render - Pass initialLocale and initialMessages as props to ClientProviders - Update LocaleProvider to accept and use server-provided initial values - Remove client-side useEffect that caused async loading delay - Export getRequestLocale() function for server-side locale detection Result: Zero FOUC - translations display instantly on page load while maintaining instant language switching via changeLocale(). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import type { Metadata, Viewport } from 'next'
|
||||
import './globals.css'
|
||||
import { ClientProviders } from '@/components/ClientProviders'
|
||||
import { getRequestLocale } from '@/i18n/request'
|
||||
import { getMessages } from '@/i18n/messages'
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Soroban Flashcard Generator',
|
||||
@@ -15,11 +17,16 @@ export const viewport: Viewport = {
|
||||
userScalable: false,
|
||||
}
|
||||
|
||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
export default async function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
const locale = await getRequestLocale()
|
||||
const messages = await getMessages(locale)
|
||||
|
||||
return (
|
||||
<html lang="en">
|
||||
<html lang={locale}>
|
||||
<body>
|
||||
<ClientProviders>{children}</ClientProviders>
|
||||
<ClientProviders initialLocale={locale} initialMessages={messages}>
|
||||
{children}
|
||||
</ClientProviders>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
|
||||
@@ -2,25 +2,29 @@
|
||||
|
||||
import { AbacusDisplayProvider } from '@soroban/abacus-react'
|
||||
import { QueryClientProvider } from '@tanstack/react-query'
|
||||
import { NextIntlClientProvider } from 'next-intl'
|
||||
import { type ReactNode, useState } from 'react'
|
||||
import { ToastProvider } from '@/components/common/ToastContext'
|
||||
import { FullscreenProvider } from '@/contexts/FullscreenContext'
|
||||
import { GameModeProvider } from '@/contexts/GameModeContext'
|
||||
import { UserProfileProvider } from '@/contexts/UserProfileContext'
|
||||
import { LocaleProvider, useLocaleContext } from '@/contexts/LocaleContext'
|
||||
import { createQueryClient } from '@/lib/queryClient'
|
||||
import { type Locale } from '@/i18n/messages'
|
||||
import { AbacusSettingsSync } from './AbacusSettingsSync'
|
||||
import { DeploymentInfo } from './DeploymentInfo'
|
||||
|
||||
interface ClientProvidersProps {
|
||||
children: ReactNode
|
||||
initialLocale: Locale
|
||||
initialMessages: Record<string, any>
|
||||
}
|
||||
|
||||
export function ClientProviders({ children }: ClientProvidersProps) {
|
||||
// Create a stable QueryClient instance that persists across renders
|
||||
const [queryClient] = useState(() => createQueryClient())
|
||||
function InnerProviders({ children }: { children: ReactNode }) {
|
||||
const { locale, messages } = useLocaleContext()
|
||||
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<NextIntlClientProvider locale={locale} messages={messages}>
|
||||
<ToastProvider>
|
||||
<AbacusDisplayProvider>
|
||||
<AbacusSettingsSync />
|
||||
@@ -34,6 +38,23 @@ export function ClientProviders({ children }: ClientProvidersProps) {
|
||||
</UserProfileProvider>
|
||||
</AbacusDisplayProvider>
|
||||
</ToastProvider>
|
||||
</NextIntlClientProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export function ClientProviders({
|
||||
children,
|
||||
initialLocale,
|
||||
initialMessages,
|
||||
}: ClientProvidersProps) {
|
||||
// Create a stable QueryClient instance that persists across renders
|
||||
const [queryClient] = useState(() => createQueryClient())
|
||||
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<LocaleProvider initialLocale={initialLocale} initialMessages={initialMessages}>
|
||||
<InnerProviders>{children}</InnerProviders>
|
||||
</LocaleProvider>
|
||||
</QueryClientProvider>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user