TanStack i18n
Guides

Change locale

Implement a reactive language switcher. Learn how to utilize createLocaleProvider and setLocale to update routing and user preference states.

To allow users to switch languages, the header needs a language switcher that updates the URL, cookie, and router in one gesture. This guide explains how to wire createLocaleProvider and build a switcher with useLocaleContext.

Prerequisites

  • Locale runtime
  • TanStack Router — app wrapped in RouterProvider
  • A hook that returns the active locale string from your message library (or route context)

Define the provider

TanStack i18n does not ship message catalogs. The provider reads locale from your hook and handles switching (persist, URL, router invalidate):

// src/i18n/provider.tsx
import { createLocaleProvider } from "@Wadiou/tanstack-i18n/react";
import { locale } from "../locale";
import { useLocale } from "./use-locale"; // wraps use-intl, route, etc.

export const { LocaleProvider, useLocaleContext } = createLocaleProvider({
  runtime: locale,
  useLocale,
});

Mount LocaleProvider inside your root route layout (nested under your message provider and router). For the complete provider layout and nesting structure, see the use-intl integration guide.

Build the switcher

function LanguageSwitcher() {
  const { locales, locale, setLocale } = useLocaleContext();

  return (
    <select
      aria-label="Language"
      value={locale}
      onChange={(e) => void setLocale(e.target.value)}
    >
      {locales.map((code) => (
        <option key={code} value={code}>
          {code === "en" ? "English" : "العربية"}
        </option>
      ))}
    </select>
  );
}

useLocaleContext fields:

FieldSource
localeYour useLocale hook
localesconfig.locales
defaultLocaleconfig.defaultLocale
setLocaleProvider — persist, URL, router invalidate

What happens on setLocale("ar")

  1. No-op if already ar
  2. All persist adapters write (LOCALE cookie, session if configured)
  3. URL updates via history.replaceState/en/about/ar/about (unless path ignored)
  4. router.invalidate() reloads loaders and your message hook picks up ar

Browser only — call setLocale from event handlers, not loaders.

Flow details: persist writes in Adapters; resolution order in Behavior contract.

Solid marketing site

Same API from @Wadiou/tanstack-i18n/solid:

import { createLocaleProvider } from "@Wadiou/tanstack-i18n/solid";

export const { LocaleProvider, useLocaleContext } = createLocaleProvider({
  runtime: locale,
  useLocale,
});

Types: CreateLocaleProviderDeps, LocaleContextValue.

How it works

TanStack i18n owns routing + persist. Your library owns strings — reload catalogs when locale changes via root beforeLoad. Full recipe: use-intl. Overview: Get started — locale vs messages.

Complete example (so far)

Header in marketing layout:

import { LanguageSwitcher } from "./LanguageSwitcher";
import { LocalizedLink } from "../i18n/routes";

function Header() {
  return (
    <header>
      <LocalizedLink to="/">Home</LocalizedLink>
      <LocalizedLink to="/about">About</LocalizedLink>
      <LanguageSwitcher />
    </header>
  );
}

API reference

createLocaleProvider({ runtime, useLocale })

Returns { LocaleProvider, useLocaleContext }. Mount LocaleProvider inside RouterProvider. Use setLocale from useLocaleContext in UI — not the core runtime directly.

What's next

Wire message catalogs: use-intl. Runtime guarantees: Behavior contract.

Edit on GitHub