# General Translation React SDKs (gt-react, gt-next, gt-react-native): React Router Quickstart
URL: https://generaltranslation.com/en-US/docs/react/react-router-quickstart.mdx
Docs index: https://generaltranslation.com/llms.txt
Description: Add General Translation to a React Router framework app, including Shopify Hydrogen storefronts, and translate your first content.

`gt-react` runs in React Router framework mode through your root route. You initialize the library in `app/root.tsx`, resolve each visitor's locale in the root loader, and render [`GTProvider`](/docs/react/reference/components/gt-provider) from the root `Layout`.

Use this quickstart for apps built with `@react-router/dev`. If you use React Router as a library inside a Vite single-page app, follow the [React SPA Quickstart](/docs/react/react-spa-quickstart) instead.

Run [`npx gt@latest init`](/docs/cli/reference/commands/init) from the app root to automate the setup. The wizard installs `gt-react`, creates the config and translation loader, and configures an `app/root.tsx` from the create-react-router or Hydrogen starter. It leaves other roots unchanged and lists any manual actions; this guide covers the same setup by hand.

*Note: This setup requires `gt-react` 11.1.3 or later and an app that renders on each request. SPA mode (`ssr: false`), pre-rendering, and RSC Framework Mode are not supported.*

## Quickstart [#quickstart]

### 1. Install the packages

`gt-react` is the library that powers translations in your app. `gt` is the CLI that generates them.

<Tabs items={['npm', 'yarn', 'bun', 'pnpm']}>
  <Tab value="npm">
    ```bash
    npm install gt-react && npm install gt --save-dev
    ```
  </Tab>

  <Tab value="yarn">
    ```bash
    yarn add gt-react && yarn add --dev gt
    ```
  </Tab>

  <Tab value="bun">
    ```bash
    bun add gt-react && bun add --dev gt
    ```
  </Tab>

  <Tab value="pnpm">
    ```bash
    pnpm add gt-react && pnpm add --save-dev gt
    ```
  </Tab>
</Tabs>

### 2. Create `gt.config.json`

Create a `gt.config.json` file in your project root. It declares your source language, your target locales, and where translation files are written.

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

- `defaultLocale` — the language your app is written in.
- `locales` — the languages to translate into. Pick from the [supported locales](/docs/platform/dashboard/reference/supported-locales).
- `files.gt.output` — where the CLI writes translation files. Keep them under `app/` so Vite bundles them with your server and client code.

### 3. Create a translation loader

Create `app/loadTranslations.ts`. It imports a locale's translation file when the root loader requests it.

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

Then create an empty `{}` file for each target locale, such as `app/_gt/es.json` and `app/_gt/ja.json`; [`gt init`](/docs/cli/reference/commands/init) creates these for you. A locale without a translation file renders in your default language.

### 4. Set up the root route

Call [`initializeGT`](/docs/react/reference/config#initialize) once at module scope in `app/root.tsx`, return the locale and a translations snapshot from the root loader, and wrap the `Layout` content in [`GTProvider`](/docs/react/reference/components/gt-provider). Add the highlighted lines to your existing root:

```tsx title="app/root.tsx"
import {
  Links,
  Meta,
  Outlet,
  Scripts,
  ScrollRestoration,
  useRouteLoaderData, // [!code ++]
} from 'react-router';
import { GTProvider, getTranslationsSnapshot, initializeGT, parseLocale } from 'gt-react'; // [!code ++]

import type { Route } from './+types/root';
import gtConfig from '../gt.config.json'; // [!code ++]
import loadTranslations from './loadTranslations'; // [!code ++]
import './app.css';

initializeGT({ ...gtConfig, loadTranslations }); // [!code ++]

// [!code ++:11]
// Error pages have no loader data. Skip GTProvider there so it doesn't
// replace the visitor's saved locale with the default.
function RootGTProvider({ children }: { children: React.ReactNode }) {
  const data = useRouteLoaderData<typeof loader>('root');
  if (!data) return <>{children}</>;
  return (
    <GTProvider locale={data.locale} translations={data.translations}>
      {children}
    </GTProvider>
  );
}

// [!code ++:4]
export async function loader({ request }: Route.LoaderArgs) {
  const locale = parseLocale(request);
  return { locale, translations: await getTranslationsSnapshot(locale) };
}

export function Layout({ children }: { children: React.ReactNode }) {
  const locale = useRouteLoaderData<typeof loader>('root')?.locale ?? gtConfig.defaultLocale; // [!code ++]
  return (
    // [!code ++]
    <html lang={locale}>
      <head>
        <meta charSet="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <Meta />
        <Links />
      </head>
      <body>
        {/* [!code ++] */}
        <RootGTProvider>
          {children}
          <ScrollRestoration />
        {/* [!code ++] */}
        </RootGTProvider>
        <Scripts />
      </body>
    </html>
  );
}

export default function App() {
  return <Outlet />;
}
```

`parseLocale` reads the locale cookie, then the `Accept-Language` header, and falls back to `defaultLocale`. If your root already has a `loader`, add `locale` and `translations` to the object it returns.

*Note: Error pages without root loader data, such as a direct 404 or a root loader error, render without [`GTProvider`](/docs/react/reference/components/gt-provider). Translation components and hooks, such as [`<T>`](/docs/react/reference/components/t) and [`useLocale`](/docs/react/reference/hooks/use-locale), throw there and turn the error page into a server error, so keep the root `ErrorBoundary` untranslated.*

### 5. Mark content for translation

Wrap JSX in the [`<T>`](/docs/react/reference/components/t) component to translate it in place, and use [`useGT`](/docs/react/reference/hooks/use-gt) for plain strings such as `aria-label` values. Add a [`<LocaleSelector>`](/docs/react/reference/components/locale-selector) so visitors can switch languages.

```tsx title="app/routes/home.tsx"
import { LocaleSelector, T, useGT } from 'gt-react';

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

  return (
    <main>
      <LocaleSelector />
      <T>
        <h1>Welcome to my app</h1>
        <p>This content is translated automatically.</p>
      </T>
      <input aria-label={gt('Email address')} />
    </main>
  );
}
```

When a visitor picks a language, [`<LocaleSelector>`](/docs/react/reference/components/locale-selector) saves it in the locale cookie and reloads the page, so the root loader renders the new locale.

### 6. Generate translations

Sign in with [`gt login`](/docs/cli/reference/commands/login), set your existing project's [`projectId`](/docs/cli/reference/config#project-id) in `gt.config.json` or `GT_PROJECT_ID` in the environment, and translate:

```bash
npx gt login
GT_PROJECT_ID=your-project-id npx gt translate
```

Signing in does not select a project. If you don't have one, create it in the [Dashboard](/docs/platform/dashboard/get-started), or run [`gt init`](/docs/cli/reference/commands/init) and opt into live development translations to choose or create one.

Start the development server and switch languages to see the translations. Prepend the command to your existing build script so production builds always include current translations:

```json title="package.json"
{
  "scripts": {
    "build": "npx gt translate && react-router build"
  }
}
```

To load translations from the CDN instead of bundling them, run [`npx gt configure --storage cdn`](/docs/cli/reference/commands/configure). It shows the change to make in `app/root.tsx`.

If you cache document responses, for example on a CDN or a reverse proxy, vary the cache by locale so visitors don't receive another language's page. Send `Vary: Cookie, Accept-Language` on document responses, or exclude them from shared caches.

<Callout type="info">
  **Note:** For CI, provide a separately scoped `GT_API_KEY` and the same project ID through your secret settings. (See [CLI credentials](/docs/cli/guides/configuring#credentials)).
</Callout>

## Shopify Hydrogen [#hydrogen]

Hydrogen storefronts are React Router framework apps and follow the same steps. Keep Hydrogen's own build command and prepend translation generation to it:

```json title="package.json"
{
  "scripts": {
    "build": "npx gt translate && shopify hydrogen build --codegen"
  }
}
```

`gt-react` translates the text in your storefront's code, such as navigation, buttons, and page copy. Product titles, descriptions, and other store content come from Shopify; translate those in Shopify.

## Troubleshooting [#troubleshooting]

<Accordions>
  <Accordion title="gt init left app/root.tsx unchanged">
    The wizard only edits roots shaped like the create-react-router or Hydrogen starter that don't already call [`initializeGT`](/docs/react/reference/config#initialize). Follow [step 4](#quickstart) to make the changes by hand.
  </Accordion>
  <Accordion title="gt init stopped before making changes">
    The reason decides the fix:

    - **SPA mode, pre-rendering, or RSC Framework Mode:** this setup reads each visitor's locale in a root loader on every request, so these modes are not supported, by the wizard or by hand.
    - **A `gt-react` range that allows versions older than 11.1.3:** upgrade `gt-react` and rerun `npx gt@latest init`.
    - **An `appDirectory` other than `app`, or a `react-router.config` the wizard cannot read:** if the app renders on each request, run `npx gt@latest init --no-react-setup` to set up everything except your source, then follow steps 2 through 4 with your app directory in place of `app/`, including in `files.gt.output`.
  </Accordion>
  <Accordion title="The language isn't changing when I use the selector">
    Confirm that cookies are enabled, the locale is in `gt.config.json`, and the selector renders inside `RootGTProvider`. If `<html lang>` changes but the text doesn't, run [`npx gt translate`](/docs/cli/reference/commands/translate) to fill `app/_gt/[locale].json`.
  </Accordion>
</Accordions>

## Next steps

- /docs/react/guides/translating-jsx
- /docs/react/guides/translating-strings
- /docs/react/guides/managing-locales
- /docs/react/guides/storing-translations

## Sitemap

See the full [sitemap](https://generaltranslation.com/sitemap.md) for all pages.
