Capítulo 21 de 24

Chapter 21: i18n with Paraglide

Core Idea

This example integrates Paraglide JS (a compiled, type-safe i18n library) with TanStack Router's URL rewriting hooks, so localized URLs (e.g. a locale prefix or path) are transparently de-localized before route matching and re-localized when generating links, while message lookups (m.home_page()) stay fully typed.

Setup

  • Routing style: file-based (src/routes/__root.tsx, src/routes/about.tsx, generated routeTree.gen.ts)
  • Key dependencies: @tanstack/react-router, @tanstack/router-plugin, @inlang/paraglide-js
  • Structure: src/main.tsx configures createRouter's rewrite option using Paraglide's deLocalizeUrl/localizeUrl. src/routes/__root.tsx sets the lang attribute, redirects via shouldRedirect, and renders a locale switcher using getLocale/setLocale/locales from the generated Paraglide runtime. Route components call generated message functions from @/paraglide/messages (aliased as m).

Code Example

// src/main.tsx
const router = createRouter({
  routeTree,
  rewrite: {
    input: ({ url }) => deLocalizeUrl(url),
    output: ({ url }) => localizeUrl(url),
  },
})

// src/routes/__root.tsx
export const Route = createRootRoute({
  beforeLoad: async () => {
    document.documentElement.setAttribute('lang', getLocale())
    const decision = await shouldRedirect({ url: window.location.href })
    if (decision.redirectUrl) {
      throw redirect({ href: decision.redirectUrl.href })
    }
  },
  component: () => (
    <>
      <Link to="/">{m.home_page()}</Link>
      <Link to="/about">{m.about_page()}</Link>
      {locales.map((locale) => (
        <button key={locale} onClick={() => setLocale(locale)}>
          {locale}
        </button>
      ))}
      <Outlet />
    </>
  ),
})
  • What it demonstrates: routing entirely in terms of un-localized paths internally, while the rewrite hook handles converting to/from locale-prefixed URLs at the router boundary, and beforeLoad enforces locale redirects before any route renders.

Key Takeaways

  1. Use the router's rewrite.input/rewrite.output pair to keep route definitions (path: '/about') locale-agnostic; Paraglide's deLocalizeUrl/localizeUrl do the translation at the router boundary, not inside every route.
  2. Put locale-redirect logic (shouldRedirect) in the root route's beforeLoad, throwing redirect({ href }), so it runs before any child route loader or component.
  3. Message functions (m.hello_about()) are generated and typed by the Paraglide compiler, so a missing translation key is a build-time error, not a runtime blank string.
  4. setLocale(locale) triggers Paraglide's own navigation/reload to apply the new locale; the router doesn't need to manually re-run loaders for a locale switch.

Connects To

  • basic-react-query (ch018): contrast the context-based data layer there with this example's near-total absence of loaders, this one is almost entirely about URL/locale handling via rewrite and beforeLoad.