# General Translation React SDKs (gt-react, gt-next, gt-react-native): React SPA Quickstart
URL: https://generaltranslation.com/en-US/docs/react/react-spa-quickstart.mdx
Docs index: https://generaltranslation.com/llms.txt
Description: Add multiple languages to a single-page React application.

By the end of this guide, your single-page React app will display content in multiple languages, with a language switcher your users can interact with.

In a single-page app, `gt-react` runs entirely in the browser — you initialize it once at startup with [`initializeGTSPA()`](/docs/react/reference/config#initialize-spa), and you don't need a provider component.

**Prerequisites:**

- A client-side rendered React app (Vite, webpack, or similar)
- Node.js 18+

Your build system may require a newer Node.js version. For example, Vite 8 requires `^20.19.0 || >=22.12.0`.

<Callout type="info">
  **Tip:** Run `npx gt@latest` to configure the Vite bootstrap and translation loading with the [setup wizard](/docs/cli/quickstart). This guide covers manual setup.
</Callout>

<Callout type="info">
  **Note:** If your app renders on the server, follow the [React Quickstart](/docs/react/react-quickstart) instead.
</Callout>

## Choose your bundler [#bundlers]

The steps below use Vite as the primary example. For another build system, use
its setup guide for the entry point, bootstrap, and translation loader:

<Cards>
  <Card title="Vite" href="/docs/react/guides/spa/configuring-vite-spa">
    Configure the Vite HTML entry and translation loading.
  </Card>
  <Card title="webpack" href="/docs/react/guides/spa/configuring-webpack-spa">
    Configure the webpack entry and translation context.
  </Card>
  <Card title="esbuild" href="/docs/react/guides/spa/configuring-esbuild-spa">
    Configure esbuild entry points and output-target fallbacks.
  </Card>
  <Card title="Rollup" href="/docs/react/guides/spa/configuring-rollup-spa">
    Configure the Rollup input and a statically analyzable locale map.
  </Card>
  <Card title="Rolldown" href="/docs/react/guides/spa/configuring-rolldown-spa">
    Configure the Rolldown input and a statically analyzable locale map.
  </Card>
  <Card title="Bazel" href="/docs/react/guides/spa/configuring-bazel-spa">
    Declare the bootstrap, configuration, package, and translations as Bazel inputs.
  </Card>
</Cards>

After setup, follow [Internationalizing a React SPA](/docs/react/guides/spa/internationalizing-react-spa)
for SPA-specific guidance on JSX, strings, locale selection, and validation.


## Quickstart [#quickstart]

### 1. Install the packages

`gt-react` is the library that powers translations in your app. `gt` is the CLI tool that prepares your translations.

<Tabs items={['npm', 'yarn', 'bun', 'pnpm']}>
  <Tab value="npm">

  ```bash
  npm i gt-react
  npm i -D gt
  ```

  </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 a translation config file

Create a **`gt.config.json`** file in your project root. This tells the library which languages you support:

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

- **`defaultLocale`** — the language your app is written in (your source language).
- **`locales`** — the languages you want to translate into. Pick any from the [supported locales list](/docs/platform/dashboard/reference/supported-locales).
- **`files`** — tells the CLI where to save translation files. The `output` path should match the import path in your [`loadTranslations`](/docs/react/reference/functions/load-translations) function (Step 3).

The CLI scans JavaScript and TypeScript files under `src`, `app`, `pages`, and `components` by default. Set [`src`](/docs/cli/reference/config#src) when your source lives elsewhere.

<Callout type="info">
  **Note:** Bundlers like Vite import translation files as modules, so translation files should live inside `src/`.
</Callout>


### 3. Create a translation loader

In an SPA, `gt-react` needs a function to load translation files in the browser at runtime. Create a [`loadTranslations`](/docs/react/reference/functions/load-translations) file:

```ts title="src/loadTranslations.ts"
export default async function loadTranslations(locale: string) {
  try {
    const translations = await import(`./_gt/${locale}.json`);
    return translations.default;
  } catch (error) {
    console.warn(`No translations found for ${locale}`);
    return {};
  }
}
```

This function loads JSON translation files from your `src/_gt/` directory. The CLI generates these files when you run [`npx gt translate`](/docs/cli/reference/commands/translate).

<Callout type="info">
  **Rollup:** Plain Rollup cannot analyze the fully dynamic import above. Use the [static locale-loader map](/docs/react/guides/developing-spa-translations#setup) instead.
</Callout>

<Accordions>
  <Accordion title="Keeping Create React App translations in public?">

Create React App can use the source-directory loader above. If you prefer to keep generated translations in `public/`, change the CLI output to `public/_gt/[locale].json`:

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

Load the file over HTTP with `PUBLIC_URL`:

```ts title="src/loadTranslations.ts"
export default async function loadTranslations(locale: string) {
  try {
    const response = await fetch(
      `${process.env.PUBLIC_URL}/_gt/${locale}.json`
    );
    if (!response.ok) throw new Error('Translation file not found');
    return await response.json();
  } catch {
    console.warn(`No translations found for ${locale}`);
    return {};
  }
}
```

  </Accordion>
</Accordions>


### 4. Initialize the library

Call **[`initializeGTSPA`](/docs/react/reference/config#initialize-spa)** once at startup, before your app renders. It takes your config and translation loader, determines the user's locale, and loads the translations for it.

The most robust pattern is a small entry module that initializes GT first, then loads the rest of the app. This allows you to translate content at the module level.

```ts title="src/index.ts"
import { initializeGTSPA } from 'gt-react';
import gtConfig from '../gt.config.json';
import loadTranslations from './loadTranslations';

await initializeGTSPA({
  ...gtConfig,
  loadTranslations,
});

await import('./main'); // render the app only after GT is ready
```

<Accordions>
  <Accordion title="Using CommonJS?">

CommonJS does not support top-level await. Wrap initialization in an async startup function and dynamically import the application afterward. This preserves the async boundary required for module-level [`t()`](/docs/react/reference/functions/t-function) calls.

```js title="src/index.js"
const { initializeGTSPA } = require('gt-react');
const gtConfig = require('../gt.config.json');

async function loadTranslations(locale) {
  try {
    return require(`./_gt/${locale}.json`);
  } catch (error) {
    console.warn(`No translations found for ${locale}`);
    return {};
  }
}

async function start() {
  await initializeGTSPA({
    ...gtConfig,
    loadTranslations,
  });

  await import('./main');
}

start().catch(console.error);
```

<Callout type="warn">
  **Warning:** Do not require `main` before initialization. Requiring it evaluates module-level [`t()`](/docs/react/reference/functions/t-function) calls before translations are ready.
</Callout>

  </Accordion>
</Accordions>

```tsx title="src/main.tsx"
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <App />
  </StrictMode>
);
```

In Vite, update the module script tag in `index.html` to point at the new entry: change its `src` from `/src/main.tsx` to `/src/index.ts`.

```html title="index.html"
<!-- <script type="module" src="/src/main.tsx"></script> -->
<script type="module" src="/src/index.ts"></script>
```

[`initializeGTSPA`](/docs/react/reference/config#initialize-spa) runs once at startup — the configuration is immutable for the lifetime of the app. After it resolves, translations can be resolved in any module. **You don't need to wrap your app in a provider.**

<Accordions>
  <Accordion title="Using Create React App?">

Create React App blocks imports from outside `src/` and does not enable top-level `await`. Keep the root `gt.config.json` for the CLI, rename your existing `src/index.tsx` entry to `src/main.tsx`, then create this new entry:

```ts title="src/index.ts"
import { initializeGTSPA } from 'gt-react';
import loadTranslations from './loadTranslations';

async function start() {
  await initializeGTSPA({
    defaultLocale: 'en',
    locales: ['es', 'fr', 'ja'],
    loadTranslations,
  });

  await import('./main');
}

start().catch(console.error);
```

Keep these locale values synchronized with the root config whenever you add or remove a language. Leave `public/index.html` unchanged; Create React App already loads `src/index`.

  </Accordion>
</Accordions>

<Callout type="info">
  **Tip:** Follow [Developing with SPA translations](/docs/react/guides/developing-spa-translations) to add the compiler and development credentials.
</Callout>


### 5. Mark content for translation

Now, wrap any text you want translated with the **[`<T>`](/docs/react/reference/components/t)** component. [`<T>`](/docs/react/reference/components/t) stands for "translate":

```tsx title="src/components/Welcome.tsx"
import { T } from 'gt-react';

export default function Welcome() {
  return (
    <main>
      <T>
        <h1>Welcome to my app</h1>
        <p>This content will be translated automatically.</p>
      </T>
    </main>
  );
}
```

You can wrap as much or as little JSX as you want inside [`<T>`](/docs/react/reference/components/t). Everything inside it — text, nested elements, even formatting — gets translated as a unit.

For strings outside React components, use **[`t()`](/docs/react/reference/functions/t-function)**. It works at module level because [`initializeGTSPA()`](/docs/react/reference/config#initialize-spa) loads translations before the rest of your app:

```ts title="src/navigation.ts"
import { t } from 'gt-react';

export const navigation = [
  { label: t('Home'), href: '/' },
  { label: t('About'), href: '/about' },
];
```


### 6. Add a language switcher

Drop in a **[`<LocaleSelector>`](/docs/react/reference/components/locale-selector)** so users can change languages:

```tsx title="src/components/Welcome.tsx"
import { T, LocaleSelector } from 'gt-react';

export default function Welcome() {
  return (
    <main>
      <LocaleSelector />
      <T>
        <h1>Welcome to my app</h1>
        <p>This content will be translated automatically.</p>
      </T>
    </main>
  );
}
```

[`LocaleSelector`](/docs/react/reference/components/locale-selector) renders a dropdown populated with the languages from your `gt.config.json`.

When the user picks a language, `gt-react` saves the choice to the `generaltranslation.locale` cookie and reloads the page — [`initializeGTSPA`](/docs/react/reference/config#initialize-spa) then re-runs and loads the new locale's translations before the app renders.


### 7. Authenticate and translate

Before translating, authenticate with General Translation:

```bash
npx gt auth
```

Follow the prompts to create an account or log in. When prompted for a key type, choose a production key. The command generates an API key and project ID, then adds them to `.env.local` in your project root:

```bash title=".env.local"
GT_PROJECT_ID="your-project-id"
GT_API_KEY="gtx-api-your-production-key"
```

<Callout type="warn">
  **Warning:** Do not commit `.env.local` or expose `GT_API_KEY` in browser code.
</Callout>

Then run the translate command to generate translation files for every configured locale:

```bash
npx gt translate
```

The CLI scans your app, translates its content, and writes the results to the output path in `gt.config.json`. Run it again whenever your source content changes.

### 8. Run and verify

Render the example component from your application:

```tsx title="src/App.tsx"
import Welcome from './components/Welcome';

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

Start your application's development server (for example, `npm run dev` in Vite), open its local URL, and select `es`, `fr`, or `ja`. Confirm that the page reloads and the heading displays the selected translation.


## Troubleshooting [#troubleshooting]

<Accordions>
  <Accordion title="The language isn't changing when I use the dropdown">

Confirm that browser cookies are enabled, the selected locale appears in `gt.config.json`, and [`initializeGTSPA`](/docs/react/reference/config#initialize-spa) resolves before the application entry module loads.

</Accordion>
  <Accordion title="Some translations are inaccurate">

Ambiguous text can lead to inaccurate translations. For example, "apple" could mean the fruit or the company. Add a `$context` prop to help:

```jsx
<T $context="the technology company">Apple</T>
```

Both [`<T>`](/docs/react/reference/components/t) and [`useGT()`](/docs/react/reference/hooks/use-gt) support the `$context` option.

</Accordion>
</Accordions>

## Next steps

- /docs/react/guides/developing-spa-translations
- /docs/react/guides/translating-jsx
- /docs/react/guides/managing-locales
- /docs/react/guides/storing-translations

## Sitemap

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