Paraglide
Integrate TanStack i18n locale routing with Paraglide JS compile-time translation functions in React and Solid applications.
Supported Frameworks: React, Solid
Runnable References:
- React Start (SSR): react-start-paraglide
- React Router (SPA): react-router-paraglide
- Solid Start (SSR): solid-start-paraglide
- Solid Router (SPA): solid-router-paraglide
Paraglide JS compiles your JSON translation files into optimized JavaScript modules. While standard i18n libraries require wrapping the application in a message context provider, Paraglide messages are pure functions that read the active language tag from its internal, global runtime.
Integrating it with TanStack i18n only requires synchronizing the router's active locale with the Paraglide runtime tags.
Responsibility split
| Layer | Owns | Package |
|---|---|---|
URL locale, redirects, cookie, setLocale | Routing + persist | @Wadiou/tanstack-i18n |
| Compile-time translation functions, parameters | Translation | @inlang/paraglide-js |
Install & Configure Paraglide
Install Paraglide CLI and package dependencies, then initialize your Inlang configuration:
pnpm add -D @inlang/paraglide-jsEnsure your project.inlang/settings.json matches your supported locales:
{
"$schema": "https://inlang.com/schema/project-settings",
"sourceLanguageTag": "en",
"languageTags": ["en", "ar"],
"modules": [
"https://cdn.jsdelivr.net/permalink/1/registry.inlang.com/plugin/m-function-matcher/v0"
]
}Define your messages in JSON catalogs (e.g. messages/en.json and messages/ar.json) and run the compiler:
pnpm paraglide-js compile --project ./project.inlangThis generates type-safe helper modules under your source folder (typically src/paraglide/).
Sync Active Locale in Router beforeLoad
Import the setLocale runtime helper from the compiled paraglide/runtime directory, and call it inside the router's beforeLoad hook. This ensures that any subsequent translation functions read the correct locale tag.
// src/routes/__root.tsx
import { createRootRouteWithContext } from "@tanstack/react-router";
import { getLocale } from "@/locale";
import { setLocale } from "@/paraglide/runtime";
export const Route = createRootRouteWithContext<{ locale: string }>()({
beforeLoad: async () => {
const active = await getLocale();
// Sync the active locale with the Paraglide runtime
setLocale(active);
return { locale: active };
}
});// src/routes/__root.tsx
import { createRootRouteWithContext } from "@tanstack/solid-router";
import { getLocale } from "@/locale";
import { setLocale } from "@/paraglide/runtime";
export const Route = createRootRouteWithContext<{ locale: string }>()({
beforeLoad: async () => {
const active = await getLocale();
// Sync the active locale with the Paraglide runtime
setLocale(active);
return { locale: active };
}
});Create the Locale Provider
Create the local bridge provider utilizing @Wadiou/tanstack-i18n framework bindings:
// src/i18n/provider.tsx
import { createLocaleProvider } from "@Wadiou/tanstack-i18n/react";
import { locale } from "@/locale";
import { Route } from "@/routes/__root";
// Bridge hook that reads the active locale from TanStack Router context
export function useLocale() {
return Route.useRouteContext().locale;
}
export const { LocaleProvider, useLocaleContext } = createLocaleProvider({
runtime: locale,
useLocale,
});// src/i18n/provider.tsx
import { createLocaleProvider } from "@Wadiou/tanstack-i18n/solid";
import { locale } from "@/locale";
import { Route } from "@/routes/__root";
// Bridge hook that reads the active locale from TanStack Router context
export function useLocale() {
return Route.useRouteContext().locale;
}
export const { LocaleProvider, useLocaleContext } = createLocaleProvider({
runtime: locale,
useLocale,
});Wrap the provider in your root component:
// src/routes/__root.tsx
import { Outlet } from "@tanstack/react-router";
import { LocaleProvider } from "@/i18n/provider";
export function RootComponent() {
return (
<LocaleProvider>
<Outlet />
</LocaleProvider>
);
}// src/routes/__root.tsx
import { Outlet } from "@tanstack/solid-router";
import { LocaleProvider } from "@/i18n/provider";
export function RootComponent() {
return (
<LocaleProvider>
<Outlet />
</LocaleProvider>
);
}Render Translations
Import your compiled messages directly and call them as functions. No hook wrappers or key dictionaries are needed:
Paraglide message functions are plain JS calls — in React, components re-render on every state change so translations always use the current locale. In Solid, JSX is evaluated once, so message functions must be wrapped with the t() utility below to reactively track locale switches.
// src/routes/{-$locale}/index.tsx
import { createFileRoute } from "@tanstack/react-router";
import * as m from "@/paraglide/messages";
export const Route = createFileRoute("/{-$locale}/")({
component: HomePage,
});
function HomePage() {
return (
<div>
<h1>{m.hello()}</h1>
<p>{m.welcome({ name: "User" })}</p>
</div>
);
}Create a small t utility that wraps message functions in a Solid memo tracking the current locale:
// src/i18n/t.ts
import { type Accessor, createMemo } from "solid-js";
import { useLocaleContext } from "./provider";
export function t<T>(fn: () => T): Accessor<T> {
const ctx = useLocaleContext();
const readLocale = () => ctx.locale;
return createMemo(() => {
readLocale();
return fn();
});
}Then use it in your components:
// src/routes/{-$locale}/index.tsx
import { createFileRoute } from "@tanstack/solid-router";
import { t } from "@/i18n/t";
import { hello, welcome } from "@/paraglide/messages";
export const Route = createFileRoute("/{-$locale}/")({
component: HomePage,
});
function HomePage() {
const tHello = t(hello);
const tWelcome = t(() => welcome({ name: "User" }));
return (
<div>
<h1>{tHello()}</h1>
<p>{tWelcome()}</p>
</div>
);
}Implement Language Switcher
Expose a UI dropdown or link switcher using the useLocaleContext hook to handle updates:
// src/components/header.tsx
import { useLocaleContext } from "@/i18n/provider";
export function Header() {
const { locale: active, setLocale } = useLocaleContext();
return (
<header>
<span>Active: {active}</span>
<button onClick={() => setLocale("ar")}>Arabic</button>
<button onClick={() => setLocale("en")}>English</button>
</header>
);
}// src/components/header.tsx
import { createSelector, For } from "solid-js";
import { useLocaleContext } from "@/i18n/provider";
export function Header() {
const { locale, setLocale } = useLocaleContext();
const isSelected = createSelector(locale);
return (
<header>
<select
onChange={(e) => setLocale(e.currentTarget.value)}
value={locale()}
>
<For each={["en", "ar"]}>
{(code) => (
<option selected={isSelected(code)} value={code}>
{code === "en" ? "English" : "Arabic"}
</option>
)}
</For>
</select>
</header>
);
}In SolidJS, dynamic <select> selection states can experience hydration mismatches on hard reloads. To ensure selection state reactivity stays synchronized, use SolidJS's createSelector utility to bind the selected attribute on <option> tags.
SPA vs. SSR Layouts
Depending on your router choice, adapt how you handle document text direction (rtl vs. ltr) and lang attributes:
-
Single Page Application (SPA): Since the DOM is always loaded, apply updates directly inside
beforeLoadon the client:beforeLoad: async () => { const active = await getLocale(); setLocale(active); document.documentElement.setAttribute("lang", active); document.documentElement.setAttribute("dir", active === "ar" ? "rtl" : "ltr"); return { locale: active }; } -
Server Side Rendering (SSR): In frameworks like TanStack Start, bind the root
<html>attributes dynamically in your root document layout based on the active router context:// src/routes/__root.tsx function RootComponent() { const { locale } = Route.useRouteContext(); return ( <html lang={locale} dir={locale === "ar" ? "rtl" : "ltr"}> {/* ... */} </html> ); }// src/routes/__root.tsx function RootComponent() { const { locale } = Route.useRouteContext(); return ( <html lang={locale} dir={locale === "ar" ? "rtl" : "ltr"}> {/* ... */} </html> ); }
Related
Solid Primitives i18n (Solid)
Combine Solid Primitives i18n with TanStack i18n locale routing. Build reactive, localized user interfaces in SolidJS applications.
Migration
Migrate your existing locale routing architecture to TanStack i18n. Transition from Next.js next-intl or Remix remix-i18next smoothly.