# General Translation Knowledge Base: How do I add i18n to TanStack Start?
URL: https://generaltranslation.com/en-US/kb/add-i18n-to-tanstack-start.mdx
Docs index: https://generaltranslation.com/llms.txt
Description: TanStack Start has no built-in i18n, so you add a library that resolves the locale for each server request, usually from a URL prefix or cookie, and loads messages before render. Paraglide JS, react-i18next, and Lingui render translations you supply; General Translation's experimental gt-tanstack-start also generates them from copy written inline in JSX.
Category: Internationalization
Last updated: 2026-10-28
Authors: General Translation Team
Published: 2026-10-28

TanStack Start doesn't include an i18n layer, so you add three pieces yourself: a library that renders text in the active language, a way to resolve that language on every server request, and the translations. Start renders the full document on the server and then hydrates it in the browser, so both sides need the same locale and the same messages, or the first paint comes out in the wrong language or fails to hydrate.

## What TanStack Start gives you, and what it leaves

TanStack Start is "a full-stack React framework powered by TanStack Router," with full-document server-side rendering (SSR), streaming, server functions, and request middleware ([TanStack Start overview](https://tanstack.com/start/latest/docs/framework/react/overview)). Several of those pieces are what an i18n setup hangs on:

- **Locale URLs.** An optional path parameter such as `/{-$locale}/about` matches `/about`, `/es/about`, and `/fr/about` with one route file, and the router's `rewrite` option can add and strip a locale prefix instead ([TanStack Router i18n guide](https://tanstack.com/router/latest/docs/guide/internationalization-i18n)).
- **Request middleware.** Middleware registered with `createStart` in `src/start.ts` runs before every request, including SSR and server functions ([TanStack Start middleware](https://tanstack.com/start/latest/docs/framework/react/guide/middleware)). That's where an i18n library reads the URL, a cookie, or the `Accept-Language` header.
- **Loaders and `head`.** A root route loader can load the locale's messages before render, and each route's `head` sets the translated `<title>` and meta tags.

What Start leaves to you: message catalogs and the runtime that looks them up, locale detection and the cookie that remembers a choice, the `<html lang>` attribute in your root shell, plural and number formatting, `hreflang` links, and the translations themselves. TanStack's i18n guide documents Paraglide JS in depth, including a TanStack Start SSR setup, and links community guides for Intlayer and use-intl. The TanStack CLI also has a `paraglide` add-on for new apps.

## Your options

| Library | How you write copy | Locale on the server | Where translations come from |
| --- | --- | --- | --- |
| [Paraglide JS](https://paraglidejs.com/tanstack-start) | Messages in JSON files per locale, compiled to typed functions such as `m.greeting()` | `paraglideMiddleware` wraps the server handler; strategies include URL, cookie, and preferred language | You, translators, or a machine-translation command you run on the files |
| [react-i18next](https://react.i18next.com/latest/ssr) | Keys in JSON namespaces, rendered with `t('home.title')` | No TanStack Start integration in its docs; you provide a per-request instance and pass `initialLanguage` and `initialI18nStore` to the client | You, translators, or a translation management system (TMS) such as Locize |
| [Lingui](https://lingui.dev/ref/vite-plugin) | `<Trans>` macros inline, extracted to PO catalogs | Its Vite plugin compiles catalogs; you activate the request's locale on the server and in the browser | You, translators, or a service synced to the catalogs |
| General Translation (`gt-tanstack-start`, experimental) | JSX wrapped in [`<T>`](/docs/react/reference/components/t), strings with [`useGT`](/docs/react/reference/hooks/use-gt) | [`gtMiddleware`](/docs/react/tanstack-start/reference/functions/gt-middleware): URL prefix (opt-in), then cookie, then `Accept-Language`, then the default locale | Generated by [`npx gt translate`](/docs/cli/reference/commands/translate) and reviewed in the Dashboard |

Use Paraglide JS when you want the setup TanStack documents, typed message functions, and per-message tree-shaking, and you already have a way to fill the JSON files. Use react-i18next when you're bringing i18next catalogs from another app. Use Lingui when your translators work in PO files. Use General Translation when you want copy to stay inline in your components and the translations generated and kept current from it. (See [General Translation vs. Paraglide JS](/kb/general-translation-vs-paraglide) for a closer comparison.)

`gt-tanstack-start` is experimental: General Translation's docs say it may have breaking changes and isn't yet recommended for production. For a stable React setup today, see [How do I translate a React app?](/kb/translate-react-app).

## Add General Translation to TanStack Start

This walkthrough adds Spanish and French to a page, translates a server function, and then moves each language to its own URL. It was tested with an app scaffolded by `npx @tanstack/cli@latest create` (`@tanstack/react-start` 1.169.4), `gt-tanstack-start@11.4.16`, and `gt@2.26.11`. The package needs `@tanstack/react-start` 1.159.0 or later and is ESM-only.

The [setup wizard](/docs/cli/reference/commands/init), `npx gt@2.26.11 init`, makes the same changes as steps 1 to 4: it installs the package and writes the loader, middleware, router call, and root route, and lists anything it can't change safely. Review its diff, or follow the steps by hand.

### 1. Install the library and CLI

```bash
npm install gt-tanstack-start@11.4.16
npm install --save-dev gt@2.26.11
```

`gt-tanstack-start` exports the shared React components, such as [`<T>`](/docs/react/reference/components/t) and [`<LocaleSelector>`](/docs/react/reference/components/locale-selector), plus server helpers. The `gt` CLI finds your source copy and writes the translation files.

### 2. Set your languages

```json title="gt.config.json"
{
  "defaultLocale": "en",
  "locales": ["es", "fr"],
  "files": {
    "gt": {
      "output": "src/_gt/[locale].json"
    }
  }
}
```

Keep the output under `src/` so Vite can import the files. Then add a loader:

```ts title="src/loadTranslations.ts"
export default async function loadTranslations(locale: string) {
  const translations = await import(`./_gt/${locale}.json`);
  return translations.default;
}
```

### 3. Register the request middleware

```ts title="src/start.ts"
import { createCsrfMiddleware, createStart } from '@tanstack/react-start';
import { gtMiddleware } from 'gt-tanstack-start';

// Keep TanStack Start's CSRF protection for server functions.
const csrfMiddleware = createCsrfMiddleware({
  filter: ({ handlerType }) => handlerType === 'serverFn',
});

export const startInstance = createStart(() => ({
  requestMiddleware: [csrfMiddleware, gtMiddleware],
}));
```

General Translation's [`gtMiddleware`](/docs/react/tanstack-start/reference/functions/gt-middleware) resolves the locale once per request, from the locale cookie, then `Accept-Language`, then `defaultLocale`, and saves it in the cookie. Each request keeps its own locale, even when several render at once.

### 4. Initialize the library and hydrate the root route

Add the initializer to your existing `src/router.tsx`, at module scope:

```tsx title="src/router.tsx"
import { initializeGT } from 'gt-tanstack-start';
import gtConfig from '../gt.config.json';
import loadTranslations from './loadTranslations';

initializeGT({ ...gtConfig, loadTranslations });

// ... your existing getRouter()
```

Then load the locale and its translations in the root route, and pass both to [`<GTProvider>`](/docs/react/reference/components/gt-provider):

```tsx title="src/routes/__root.tsx"
import { HeadContent, Scripts, createRootRoute } from '@tanstack/react-router';
import {
  GTProvider,
  LocaleSelector,
  getLocale,
  getTranslationsSnapshot,
} from 'gt-tanstack-start';

export const Route = createRootRoute({
  // Runs on the server for the first request, so the HTML is already translated.
  loader: async () => {
    const locale = getLocale();
    return { locale, translations: await getTranslationsSnapshot(locale) };
  },
  shellComponent: RootDocument,
});

function RootDocument({ children }: { children: React.ReactNode }) {
  const { locale, translations } = Route.useLoaderData();
  return (
    <html lang={locale}>
      <head>
        <HeadContent />
      </head>
      <body>
        <GTProvider locale={locale} translations={translations}>
          <LocaleSelector />
          {children}
        </GTProvider>
        <Scripts />
      </body>
    </html>
  );
}
```

[`getLocale`](/docs/react/tanstack-start/reference/functions/get-locale) reads the request's locale on the server and the cookie in the browser, so both renders agree. Setting `lang={locale}` replaces the hardcoded `lang="en"` in the starter's shell.

### 5. Mark content for translation

```tsx title="src/routes/index.tsx"
import { createFileRoute } from '@tanstack/react-router';
import { T, useGT } from 'gt-tanstack-start';

export const Route = createFileRoute('/')({ component: Home });

function Home() {
  const gt = useGT();

  return (
    <main>
      <T>
        <h1>Find your next project</h1>
        <p>Explore projects from teams around the world.</p>
      </T>
      <input type="search" placeholder={gt('Search projects')} aria-label={gt('Search projects')} />
    </main>
  );
}
```

[`<T>`](/docs/react/reference/components/t) translates the heading and paragraph as one unit, with the markup as context. [`useGT`](/docs/react/reference/hooks/use-gt) covers attribute strings. For names, counts, and dates inside [`<T>`](/docs/react/reference/components/t), use the [variable and formatting components](/docs/react/guides/formatting-variables).

### 6. Generate translations

Sign in with [`npx gt login`](/docs/cli/reference/commands/login), set `GT_PROJECT_ID` (or `projectId` in `gt.config.json`), and run [`npx gt translate`](/docs/cli/reference/commands/translate). It writes `src/_gt/es.json` and `src/_gt/fr.json`. Generation uses [usage-based pricing](https://generaltranslation.com/pricing).

To try the setup without an account, run [`npx gt generate`](/docs/cli/reference/commands/generate). It writes the same files with the English source text, which you edit by hand. A locale with no file yet renders the source text and logs a warning that its translations couldn't load.

### 7. Run it and switch languages

Run `npm run dev` and pick Español in the selector. It saves the choice in the locale cookie and reloads, and the server renders Spanish. In the test app, a production build served `<html lang="es">` with the Spanish heading and placeholder for a request with `Accept-Language: es`, and `<html lang="en">` with English for a request with no preference.

## Translate in server functions and metadata

[`getGT`](/docs/react/tanstack-start/reference/functions/get-gt) translates strings inside a server function, using the request's locale:

```tsx title="src/routes/account.tsx"
import { createFileRoute } from '@tanstack/react-router';
import { createServerFn } from '@tanstack/react-start';
import { getGT } from 'gt-tanstack-start';

const loadAccount = createServerFn({ method: 'GET' }).handler(async () => {
  const gt = await getGT();
  return { title: gt('Your account') };
});

export const Route = createFileRoute('/account')({
  loader: () => loadAccount(),
  // The translated title also becomes the page's <title>.
  head: ({ loaderData }) => ({ meta: [{ title: loaderData?.title }] }),
  component: () => <h1>{Route.useLoaderData().title}</h1>,
});
```

These helpers throw on the server outside a request that [`gtMiddleware`](/docs/react/tanstack-start/reference/functions/gt-middleware) handled, such as at module scope. [Using server functions](/docs/react/tanstack-start/using-server-functions) covers dictionary entries and registered messages too.

## Give each language its own URL

The setup above serves every language at the same URL, chosen by the cookie or browser language. Search engines index one language per URL, so [public pages need a prefix](/kb/locale-routing-seo) such as `/es/about`. Move routes under an optional segment, such as `src/routes/{-$locale}/index.tsx` with `createFileRoute('/{-$locale}/')`, then turn on locale routing:

```json title="gt.config.json"
{
  "defaultLocale": "en",
  "locales": ["es", "fr"],
  "localeRouting": true
}
```

The middleware then checks the path first, and the selector navigates to the prefixed URL. In the test app, `/es` and `/es/account` rendered Spanish with `<html lang="es">`, and `/` rendered English. An unprefixed URL still follows the cookie for a returning visitor, and you add [`hreflang` links](/kb/what-is-hreflang) in each route's `head` yourself. (See the [locale routing setup](/docs/react/tanstack-start/setup#locale-routing).)

## Ship and keep translations current

Translate before each build so new copy ships in every language:

```json title="package.json"
{
  "scripts": {
    "build": "gt validate && gt translate && vite build"
  }
}
```

[`gt validate`](/docs/cli/reference/commands/validate) fails the build on content that would break translation, before any API call. In CI, set `GT_PROJECT_ID` and a project API key as `GT_API_KEY` with **Files > Write** and **Translation queue > Enabled** ([CLI credentials](/docs/cli/guides/configuring#credentials)). Later runs translate only new or changed source.

Reviewers can edit any string in General Translation's [Translation Editor](/docs/platform/dashboard/guides/reviewing-translations), and [`npx gt save-local`](/docs/cli/reference/commands/save-local) sends edits made in the JSON files back so later runs keep them. A [Context Group](/docs/platform/dashboard/guides/defining-context-for-translations) holds your Glossary and Custom Prompts, so product names and tone match across this app, your docs, and your other projects. [Locadex](/docs/platform/locadex/guides/configuring-automations), General Translation's AI agent, supports TanStack Start apps and can translate new copy through pull requests.

## Common questions

### Does TanStack Start have built-in i18n?

No. TanStack Router gives you optional locale segments and URL rewrites, and Start gives you middleware and loaders, but messages, locale detection, and translations come from a library.

### Can I use react-i18next with TanStack Start?

Yes, as a React library. TanStack doesn't document it, and react-i18next's SSR guide names Next.js, Remix, and Gatsby integrations, so you wire the per-request instance and client hydration yourself.

### Is gt-tanstack-start ready for production?

Not yet: General Translation marks it experimental. Pin the version, and review the changelog before upgrading.

## Key takeaways

- TanStack Start has no i18n layer; its optional `/{-$locale}` segments, `createStart` middleware, and root loaders are where you attach one.
- Resolve the locale per request on the server and hydrate the client with the same messages, and set `<html lang>` from the loader instead of the starter's hardcoded `lang="en"`.
- Paraglide JS is the library TanStack documents; it, react-i18next, and Lingui all render translations you supply.
- General Translation's experimental `gt-tanstack-start` adds [`gtMiddleware`](/docs/react/tanstack-start/reference/functions/gt-middleware), [`initializeGT`](/docs/react/reference/config#initialize), and [`<GTProvider>`](/docs/react/reference/components/gt-provider), keeps copy inline in [`<T>`](/docs/react/reference/components/t), and [`npx gt translate`](/docs/cli/reference/commands/translate) writes `src/_gt/[locale].json`.
- Add `"localeRouting": true` with an optional `{-$locale}` segment when each language needs its own indexable URL.

## Related questions

- [How do I add i18n to a Next.js App Router app?](https://generaltranslation.com/en-US/kb/add-i18n-to-nextjs.mdx): Start with gt init to configure General Translation in a supported App Router project, then mark source content, generate translations, and verify language switching. gt-next keeps copy in JSX with translation context; the CLI, Dashboard editor, and Locadex handle generation, review, and ongoing updates. This guide also shows the explicit local-file setup.
- [General Translation vs. Paraglide JS: which should you use?](https://generaltranslation.com/en-US/kb/general-translation-vs-paraglide.mdx): Choose General Translation to keep copy inline and generate translations with the structure of your React components, shared terminology, and Dashboard review. Choose Paraglide JS for compiled, typed message functions and per-message tree-shaking. Paraglide renders your catalogs; General Translation connects source copy to translation generation and ongoing updates.
- [What's the best i18n library for a React app?](https://generaltranslation.com/en-US/kb/best-react-i18n-library.mdx): The four main React i18n libraries are General Translation's gt-react, react-i18next, Lingui, and React Intl. All four render and format translations, but only gt-react also generates them, from copy written inline in your JSX. Choose react-i18next to keep i18next catalogs and plugins, Lingui for extraction to compiled catalogs, and React Intl for FormatJS and ICU MessageFormat.
- [Language redirects and SEO: URL, cookie, or Accept-Language?](https://generaltranslation.com/en-US/kb/locale-routing-seo.mdx): Let the URL decide the language of every page you want indexed, because Googlebot sends no Accept-Language header and crawls mostly from US addresses. Use a saved cookie choice, then Accept-Language, only to pick where a neutral root such as / redirects, with a temporary 302 or 307.
- [How do I translate a React app?](https://generaltranslation.com/en-US/kb/translate-react-app.mdx): Translating a React app means internationalizing its components so they render in the user's language, then translating every string into each language you support. General Translation's gt-react translates JSX in place in the `<T>` component, and npx gt translate writes one file per language.

## Previous and next questions

- Previous question: [How do I add i18n to a Next.js Pages Router app?](https://generaltranslation.com/en-US/kb/add-i18n-to-nextjs-pages-router.mdx)
- Next question: [How do I create a sitemap for a multilingual website?](https://generaltranslation.com/en-US/kb/multilingual-sitemap.mdx)

## Sitemap

See the [Knowledge Base sitemap](https://generaltranslation.com/en-US/kb/sitemap.md) for every article.
