product icon

Paraglide JS

App

React Router v7 (framework) example

This example shows how to use Paraglide with React Router v7. The source code can be found here.

Getting started

  1. Init Paraglide JS
npx @inlang/paraglide-js@latest init 
  1. Add the vite plugin to your vite.config.ts:
import { reactRouter } from "@react-router/dev/vite";
import { defineConfig } from "vite";
+import { paraglideVitePlugin } from "@inlang/paraglide-js";

export default defineConfig({
	plugins: [
		reactRouter(),
+		paraglideVitePlugin({
+			project: "./project.inlang",
+			outdir: "./app/paraglide",
+		}),
	],
});
  1. Done :)

Run the app and start translating. See the basics documentation for information on how to use Paraglide's messages, parameters, and locale management.

Server side rendering using middleware

In your middleware file:

import { paraglideMiddleware } from "~/paraglide/server";
import type { Route } from "../+types/root";

const localeMiddleware: Route.unstable_MiddlewareFunction = async (
  { request },
  next,
) => {
  return await paraglideMiddleware(request, () => {
    return next();
  }, { onRedirect: (response) => { throw response } });
};

export { localeMiddleware };

In root.tsx:

export const unstable_middleware = [localeMiddleware];

In routes.ts:

import {
	type RouteConfig,
	index,
	prefix,
	route,
} from "@react-router/dev/routes";

export default [
	// prefixing each path with an optional :locale
	// optional to match a path with no locale `/page`
	// or with a locale `/en/page`
	//
	// * make sure that the pattern you define here matches
	// * with the urlPatterns of paraglide JS if you use
	// * the `url` strategy
+	...prefix(":locale?", [
		index("routes/home.tsx"),
		route("about", "routes/about.tsx"),
+	]),
] satisfies RouteConfig;

Now you can use getLocale function anywhere in your project.

Server side rendering without middleware

If you use React Router v7 with SSR you will need to add the following code:

In root.tsx:

import {
	assertIsLocale,
	baseLocale,
	getLocale,
	isLocale,
	overwriteGetLocale,
} from "./paraglide/runtime";

+export function loader(args: Route.LoaderArgs) {
+	return {
		// detect the locale from the path. if no locale is found, the baseLocale is used.
		// e.g. /de will set the locale to "de"
+		locale: isLocale(args.params.locale) ? args.params.locale : baseLocale,
+	};
}

// server-side rendering needs to be scoped to each request
// react context is used to scope the locale to each request
// and getLocale() is overwritten to read from the react context
+const LocaleContextSSR = createContext(baseLocale);
+if (import.meta.env.SSR) {
+	overwriteGetLocale(() => assertIsLocale(useContext(LocaleContextSSR)));
+}

export default function App(props: Route.ComponentProps) {
	return (
		// use the locale
+		<LocaleContextSSR.Provider value={props.loaderData.locale}>
			<Outlet />
+		</LocaleContextSSR.Provider>
	);
}

Derive the loader's return type once so you can share it with the meta function:

type RootLoaderData = Awaited<ReturnType<typeof loader>>;

Meta generation

React Router generates the <meta> tags in a separate context on the client. To make sure the getLocale helper returns the correct locale during the generation step, update your meta function like this:

export function meta({ data }: Route.MetaArgs) {
  if (!import.meta.env.SSR) {
    const locale = assertIsLocale(
      (data as RootLoaderData | undefined)?.locale ?? baseLocale,
    );

    overwriteGetLocale(() => locale);
  } else {
    overwriteGetLocale(() =>
      assertIsLocale(useContext<Locale>(LocaleContextSSR))
    );
  }

  return [];
}

This mirrors the server setup shown above while ensuring that the meta generation step runs with the correct locale, even when multiple requests are handled in parallel. Access the locale returned by your root loader through the data (aka loaderData) argument instead of matches.

In routes.ts:

import {
	type RouteConfig,
	index,
	prefix,
	route,
} from "@react-router/dev/routes";

export default [
	// prefixing each path with an optional :locale
	// optional to match a path with no locale `/page`
	// or with a locale `/en/page`
	//
	// * make sure that the pattern you define here matches
	// * with the urlPatterns of paraglide JS if you use
	// * the `url` strategy
+	...prefix(":locale?", [
		index("routes/home.tsx"),
		route("about", "routes/about.tsx"),
+	]),
] satisfies RouteConfig;