Lesson 4: Client with Context Mode (SPA)
In this lesson, you will learn how to configure lingui-rr for a Client-Only / SPA application (server: false) that keeps clean URLs (mode: 'context') and stores the language selection in the browser (e.g. localStorage or cookies).
Step 1: Configure the Router i18n Object
Set server: false and mode: 'context'. In a pure client-only SPA, you can use localStorage to persist the selected language, which avoids the need to send cookies to a backend server.
ts
// app/lib/i18n.ts
import {
createLinguiRouter,
clientDetectors,
clientPersistence,
} from 'lingui-rr'
export const locales = ['en', 'ar']
export const defaultLocale = 'en'
export const i18n = createLinguiRouter({
server: false, // SPA Mode
mode: 'context', // Clean URLs (no prefix)
locales,
defaultLocale,
detection: [
clientDetectors.localStorage('locale'), // Detect from localStorage
clientDetectors.navigator(), // Fallback to browser language
],
persistence: [clientPersistence.localStorage('locale')], // Save to localStorage
ignorePaths: [
/^\/assets\//,
/^\/build\//,
/^\/favicon\.ico$/,
],
catalogs: {
en: () => import('../locales/en.po'),
ar: () => import('../locales/ar.po'),
},
})Step 2: Wire the Root Route (root.tsx)
In your root route, export the client data functions.
tsx
// app/root.tsx
import {
createLinguiClientMiddleware,
createLinguiRootLoader,
LinguiRouterProvider,
} from 'lingui-rr'
import {
Links,
Meta,
Outlet,
Scripts,
ScrollRestoration,
useLoaderData,
useRouteLoaderData,
} from 'react-router'
import { i18n } from './lib/i18n'
export const clientMiddleware = [createLinguiClientMiddleware(i18n)]
export const clientLoader = createLinguiRootLoader(i18n)
export function Layout({ children }: { readonly children: React.ReactNode }) {
const lingui = useRouteLoaderData<typeof clientLoader>('root')
return (
<html
lang={lingui?.locale ?? 'en'}
dir={lingui?.htmlAttrs.dir ?? 'ltr'}
suppressHydrationWarning
>
<head>
<meta charSet="utf-8" />
<meta content="width=device-width, initial-scale=1" name="viewport" />
<Meta />
<Links />
</head>
<body>
{children}
<ScrollRestoration />
<Scripts />
</body>
</html>
)
}
export default function App() {
const lingui = useLoaderData<typeof clientLoader>()
return (
<LinguiRouterProvider state={lingui} >
<Outlet />
</LinguiRouterProvider>
)
}If you want the optional locale-action revalidation guardrail, add the same createLinguiShouldRevalidate(i18n) export shown in Lesson 3.
Browser-Only vs. Isomorphic Storage
In Lesson 2 and 4, we introduce two kinds of client-side storage:
- Cookies (
clientPersistence.cookie): These are isomorphic. In a React Router framework mode app, even withserver: false, standard action forms can submit requests to server actions. Using cookies ensures that the browser-side storage is automatically mirrored via theSet-Cookieheader during action redirects. - LocalStorage (
clientPersistence.localStorage): This is pure browser-only storage. If your app is served statically (e.g. from static hostings with no server-side execution at all),localStorageis the ideal solution. It avoids cookie headers and maintains user state locally in the user's browser.