How to build a language switcher component in React Router v7
Switch languages while staying on the same page
Problem
Users expect language switchers to preserve their current context. When browsing a product page, help article, or account settings, switching from English to Spanish should display that same page in Spanish. Instead, many implementations treat language selection as a navigation event that redirects users to the homepage in the new language, forcing them to navigate back to where they were. This breaks the user's flow and creates frustration, especially in content-heavy applications where users may be deep in a navigation hierarchy.
The root cause is that language switchers often use hardcoded destination URLs rather than dynamically constructing URLs based on the current page. Without reading and transforming the current URL structure, the switcher cannot maintain the user's position in the application across language changes.
Solution
Build a language switcher component that reads the current URL and extracts both the active locale parameter and the remaining path segments. For each supported language, generate a new URL by replacing only the locale segment while keeping all other path segments and query parameters intact. Render these URLs as links so users can switch languages without losing their place in the application.
This approach treats the locale as a replaceable parameter in the URL structure rather than a navigation destination, ensuring that switching from /en/products/shoes to /es/products/shoes preserves the user's context.
Steps
1. Create a helper function to build locale-aware URLs
Define a function that takes the current pathname and a target locale, then constructs a new path by replacing the locale segment.
export function buildLocalePath(
currentPath: string,
newLocale: string,
): string {
const segments = currentPath.split("/").filter(Boolean);
if (segments.length === 0) {
return `/${newLocale}`;
}
segments[0] = newLocale;
return `/${segments.join("/")}`;
}
This function splits the pathname into segments, replaces the first segment with the target locale, and reconstructs the path. It handles edge cases like the root path and ensures the locale is always the first segment.
2. Define your supported locales
Create a configuration object that lists all languages your application supports.
export const locales = [
{ code: "en", label: "English" },
{ code: "es", label: "Español" },
{ code: "fr", label: "Français" },
{ code: "de", label: "Deutsch" },
];
This configuration serves as the source of truth for which languages to display in the switcher and provides user-friendly labels for each locale.
3. Build the language switcher component
Create a component that reads the current location, determines the active locale, and renders links for all other supported languages.
import { Link, useLocation, useParams } from "react-router";
import { locales, buildLocalePath } from "./i18n-config";
export function LanguageSwitcher() {
const location = useLocation();
const params = useParams();
const currentLocale = params.locale || "en";
return (
<nav aria-label="Language switcher">
<ul>
{locales.map((locale) => {
const isActive = locale.code === currentLocale;
const newPath = buildLocalePath(location.pathname, locale.code);
return (
<li key={locale.code}>
{isActive ? (
<span aria-current="true">{locale.label}</span>
) : (
<Link to={newPath}>{locale.label}</Link>
)}
</li>
);
})}
</ul>
</nav>
);
}
The component uses useLocation to access the current pathname and useParams to extract the active locale from the URL. For each supported locale, it generates a new path using the helper function and renders either a link or a non-interactive element for the current language.
4. Preserve query parameters and hash fragments
Extend the helper function to maintain query strings and URL fragments when switching languages.
export function buildLocalePath(
currentPath: string,
search: string,
hash: string,
newLocale: string,
): string {
const segments = currentPath.split("/").filter(Boolean);
if (segments.length === 0) {
return `/${newLocale}${search}${hash}`;
}
segments[0] = newLocale;
return `/${segments.join("/")}${search}${hash}`;
}
This updated version accepts the search and hash properties from the location object and appends them to the generated path, ensuring that filters, sorting parameters, and anchor links survive the language switch.
5. Update the component to use the enhanced helper
Modify the switcher to pass the full location information to the helper function.
import { Link, useLocation, useParams } from "react-router";
import { locales, buildLocalePath } from "./i18n-config";
export function LanguageSwitcher() {
const location = useLocation();
const params = useParams();
const currentLocale = params.locale || "en";
return (
<nav aria-label="Language switcher">
<ul>
{locales.map((locale) => {
const isActive = locale.code === currentLocale;
const newPath = buildLocalePath(
location.pathname,
location.search,
location.hash,
locale.code,
);
return (
<li key={locale.code}>
{isActive ? (
<span aria-current="true">{locale.label}</span>
) : (
<Link to={newPath}>{locale.label}</Link>
)}
</li>
);
})}
</ul>
</nav>
);
}
The component now passes location.search and location.hash to the helper, ensuring that URLs like /en/products?category=shoes#reviews become /es/products?category=shoes#reviews when switching to Spanish.