diff --git a/docs/router/config.json b/docs/router/config.json
index 808c3fe38f9..a1fab7b8bc9 100644
--- a/docs/router/config.json
+++ b/docs/router/config.json
@@ -324,6 +324,10 @@
{
"label": "Render Optimizations",
"to": "framework/react/guide/render-optimizations"
+ },
+ {
+ "label": "Internationalization (i18n)",
+ "to": "framework/react/guide/internationalization-i18n"
}
]
},
diff --git a/docs/router/framework/react/guide/internationalization-i18n.md b/docs/router/framework/react/guide/internationalization-i18n.md
index 49397717772..06a6bd544d5 100644
--- a/docs/router/framework/react/guide/internationalization-i18n.md
+++ b/docs/router/framework/react/guide/internationalization-i18n.md
@@ -11,33 +11,16 @@ This guide covers:
- Language navigation and switching
- SEO considerations
- Type safety
-- Integration patterns with i18n libraries (Paraglide)
+- Integration patterns with i18n libraries (e.g. Paraglide)
----
-
-## i18n with Optional Path Parameters
-
-This pattern relies exclusively on TanStack Router features. It is suitable when:
-
-- You want full control over translations
-- You already manage translations manually
-- You do not need automatic locale detection
-
-Optional path parameters are ideal for implementing locale-aware routing without duplicating routes.
-
-```ts
-;/{-$locale}/abotu
-```
+## Internationalization (i18n) with Optional Path Parameters
-This single route matches:
-
-- `/about` (default locale)
-- `/en/about`
-- `/fr/about`
-- `/es/about`
+Optional path parameters are excellent for implementing internationalization (i18n) routing patterns. You can use prefix patterns to handle multiple languages while maintaining clean, SEO-friendly URLs.
### Prefix-based i18n
+Use optional language prefixes to support URLs like `/en/about`, `/fr/about`, or just `/about` (default language):
+
```tsx
// Route: /{-$locale}/about
export const Route = createFileRoute('/{-$locale}/about')({
@@ -46,86 +29,328 @@ export const Route = createFileRoute('/{-$locale}/about')({
function AboutComponent() {
const { locale } = Route.useParams()
- const currentLocale = locale || 'en'
+ const currentLocale = locale || 'en' // Default to English
const content = {
- en: { title: 'About Us' },
- fr: { title: 'À Propos' },
- es: { title: 'Acerca de' },
+ en: { title: 'About Us', description: 'Learn more about our company.' },
+ fr: {
+ title: 'À Propos',
+ description: 'En savoir plus sur notre entreprise.',
+ },
+ es: {
+ title: 'Acerca de',
+ description: 'Conoce más sobre nuestra empresa.',
+ },
}
- return
{content[currentLocale].title}
+ return (
+
+
{content[currentLocale]?.title}
+
{content[currentLocale]?.description}
+
+ )
}
```
-### Complex Routing Patterns
+This pattern matches:
+
+- `/about` (default locale)
+- `/en/about` (explicit English)
+- `/fr/about` (French)
+- `/es/about` (Spanish)
+
+### Complex i18n Patterns
+
+Combine optional parameters for more sophisticated i18n routing:
```tsx
// Route: /{-$locale}/blog/{-$category}/$slug
export const Route = createFileRoute('/{-$locale}/blog/{-$category}/$slug')({
- beforeLoad: ({ params }) => {
+ beforeLoad: async ({ params }) => {
const locale = params.locale || 'en'
- const validLocales = ['en', 'fr', 'es', 'de']
+ const category = params.category
- if (params.locale && !validLocales.includes(params.locale)) {
+ // Validate locale and category
+ const validLocales = ['en', 'fr', 'es', 'de']
+ if (locale && !validLocales.includes(locale)) {
throw new Error('Invalid locale')
}
- return { locale }
+ return { locale, category }
+ },
+ loader: async ({ params, context }) => {
+ const { locale } = context
+ const { slug, category } = params
+
+ return fetchBlogPost({ slug, category, locale })
},
+ component: BlogPostComponent,
})
+
+function BlogPostComponent() {
+ const { locale, category, slug } = Route.useParams()
+ const data = Route.useLoaderData()
+
+ return (
+
+ {data.title}
+
+ Category: {category || 'All'} | Language: {locale || 'en'}
+
+ {data.content}
+
+ )
+}
```
-### Language Switching
+This supports URLs like:
+
+- `/blog/tech/my-post` (default locale, tech category)
+- `/fr/blog/my-post` (French, no category)
+- `/en/blog/tech/my-post` (explicit English, tech category)
+- `/es/blog/tecnologia/mi-post` (Spanish, Spanish category)
+
+### Language Navigation
+
+Create language switchers using optional i18n parameters with function-style params:
```tsx
- ({
- ...prev,
- locale: prev.locale === 'en' ? undefined : 'fr',
- })}
->
- Français
-
+function LanguageSwitcher() {
+ const currentParams = useParams({ strict: false })
+
+ const languages = [
+ { code: 'en', name: 'English' },
+ { code: 'fr', name: 'Français' },
+ { code: 'es', name: 'Español' },
+ ]
+
+ return (
+
+ {languages.map(({ code, name }) => (
+ ({
+ ...prev,
+ locale: code === 'en' ? undefined : code, // Remove 'en' for clean URLs
+ })}
+ className={currentParams.locale === code ? 'active' : ''}
+ >
+ {name}
+
+ ))}
+
+ )
+}
```
-### Type-safe Locales
+You can also create more sophisticated language switching logic:
-```ts
-type Locale = 'en' | 'fr' | 'es' | 'de'
+```tsx
+function AdvancedLanguageSwitcher() {
+ const currentParams = useParams({ strict: false })
+
+ const handleLanguageChange = (newLocale: string) => {
+ return (prev: any) => {
+ // Preserve all existing params but update locale
+ const updatedParams = { ...prev }
+
+ if (newLocale === 'en') {
+ // Remove locale for clean English URLs
+ delete updatedParams.locale
+ } else {
+ updatedParams.locale = newLocale
+ }
+
+ return updatedParams
+ }
+ }
-function isLocale(value?: string): value is Locale {
- return ['en', 'fr', 'es', 'de'].includes(value as Locale)
+ return (
+
+
+ Français
+
+
+
+ Español
+
+
+
+ English
+
+
+ )
}
```
----
+### Advanced i18n with Optional Parameters
-## i18n Library Integration Patterns
+Organize i18n routes using optional parameters for flexible locale handling:
-TanStack Router is **library-agnostic**. You can integrate any i18n solution by mapping locale state to routing behavior.
+```tsx
+// Route structure:
+// routes/
+// {-$locale}/
+// index.tsx // /, /en, /fr
+// about.tsx // /about, /en/about, /fr/about
+// blog/
+// index.tsx // /blog, /en/blog, /fr/blog
+// $slug.tsx // /blog/post, /en/blog/post, /fr/blog/post
+
+// routes/{-$locale}/index.tsx
+export const Route = createFileRoute('/{-$locale}/')({
+ component: HomeComponent,
+})
-Below is a recommended pattern using **Paraglide**.
+function HomeComponent() {
+ const { locale } = Route.useParams()
+ const isRTL = ['ar', 'he', 'fa'].includes(locale || '')
+
+ return (
+
+
Welcome ({locale || 'en'})
+ {/* Localized content */}
+
+ )
+}
----
+// routes/{-$locale}/about.tsx
+export const Route = createFileRoute('/{-$locale}/about')({
+ component: AboutComponent,
+})
+```
+
+### SEO and Canonical URLs
+
+Handle SEO for i18n routes properly:
+
+```tsx
+export const Route = createFileRoute('/{-$locale}/products/$id')({
+ component: ProductComponent,
+ head: ({ params, loaderData }) => {
+ const locale = params.locale || 'en'
+ const product = loaderData
+
+ return {
+ title: product.title[locale] || product.title.en,
+ meta: [
+ {
+ name: 'description',
+ content: product.description[locale] || product.description.en,
+ },
+ {
+ property: 'og:locale',
+ content: locale,
+ },
+ ],
+ links: [
+ // Canonical URL (always use default locale format)
+ {
+ rel: 'canonical',
+ href: `https://example.com/products/${params.id}`,
+ },
+ // Alternate language versions
+ {
+ rel: 'alternate',
+ hreflang: 'en',
+ href: `https://example.com/products/${params.id}`,
+ },
+ {
+ rel: 'alternate',
+ hreflang: 'fr',
+ href: `https://example.com/fr/products/${params.id}`,
+ },
+ {
+ rel: 'alternate',
+ hreflang: 'es',
+ href: `https://example.com/es/products/${params.id}`,
+ },
+ ],
+ }
+ },
+})
+```
+
+### Type Safety for i18n
+
+Ensure type safety for your i18n implementations:
+
+```tsx
+// Define supported locales
+type Locale = 'en' | 'fr' | 'es' | 'de'
+
+// Type-safe locale validation
+function validateLocale(locale: string | undefined): locale is Locale {
+ return ['en', 'fr', 'es', 'de'].includes(locale as Locale)
+}
-## Client-side i18n with a Library (TanStack Router)
+export const Route = createFileRoute('/{-$locale}/shop/{-$category}')({
+ beforeLoad: async ({ params }) => {
+ const { locale } = params
-This pattern combines TanStack Router with a client-side i18n library. It is suitable when:
+ // Type-safe locale validation
+ if (locale && !validateLocale(locale)) {
+ throw redirect({
+ to: '/shop/{-$category}',
+ params: { category: params.category },
+ })
+ }
+
+ return {
+ locale: (locale as Locale) || 'en',
+ isDefaultLocale: !locale || locale === 'en',
+ }
+ },
+ component: ShopComponent,
+})
-- You want type-safe translations
-- You want localized URLs
-- You do not need server-side rendering
+function ShopComponent() {
+ const { locale, category } = Route.useParams()
+ const { isDefaultLocale } = Route.useRouteContext()
+
+ // TypeScript knows locale is Locale | undefined
+ // and we have validated it in beforeLoad
+
+ return (
+
+
Shop {category ? `- ${category}` : ''}
+
Language: {locale || 'en'}
+ {!isDefaultLocale && (
+
+ View in English
+
+ )}
+
+ )
+}
+```
-### TanStack Router + Paraglide (Client-only)
+Optional path parameters provide a powerful and flexible foundation for implementing internationalization in your TanStack Router applications. Whether you prefer prefix-based or combined approaches, you can create clean, SEO-friendly URLs while maintaining excellent developer experience and type safety.
+
+## i18n Library Integration Patterns
+
+TanStack Router is **library-agnostic**. You can integrate any i18n solution by mapping locale state to routing behavior.
+
+Below is a recommended pattern using **Paraglide**.
+
+### TanStack Router + Paraglide
Paraglide provides type-safe translations, locale detection, and URL localization that pair naturally with TanStack Router.
**GitHub example:**
[https://github.com/TanStack/router/tree/main/examples/react/i18n-paraglide](https://github.com/TanStack/router/tree/main/examples/react/i18n-paraglide)
-### Project Setup
+#### Project Setup
```bash
npx @inlang/paraglide-js@latest init
@@ -140,7 +365,7 @@ paraglideVitePlugin({
})
```
-### URL Localization via Router Rewrite
+#### URL Localization via Router rewrite
```ts
import { deLocalizeUrl, localizeUrl } from './paraglide/runtime'
@@ -154,45 +379,7 @@ const router = createRouter({
})
```
----
-
-## Server-side i18n (TanStack Start)
-
-This pattern integrates i18n at the routing and server layers. It is suitable when:
-
-- You use TanStack Start
-- You need SSR or streaming
-- You want locale-aware redirects and metadata
-
-### TanStack Start + Paraglide
-
-**GitHub example:**
-[https://github.com/TanStack/router/tree/main/examples/react/start-i18n-paraglide](https://github.com/TanStack/router/tree/main/examples/react/start-i18n-paraglide)
-
-### Server Middleware (SSR)
-
-```ts
-import { paraglideMiddleware } from './paraglide/server'
-
-export default {
- fetch(req: Request) {
- return paraglideMiddleware(req, () => handler.fetch(req))
- },
-}
-```
-
-### HTML Language Attribute
-
-```tsx
-import { getLocale } from '../paraglide/runtime'
-;