# General Translation Knowledge Base: How do I migrate from react-i18next to gt-react?
URL: https://generaltranslation.com/en-GB/kb/migrate-from-react-i18next.mdx
Docs index: https://generaltranslation.com/llms.txt
Description: Install gt-react next to react-i18next, share one locale cookie, and move one component at a time from t() keys and `<Trans>` to inline copy in `<T>` and useGT. General Translation translates the moved copy with its JSX as context, and your team can correct it in the Dashboard. Unmoved components keep their reviewed react-i18next translations.
Category: Internationalisation
Last updated: 2026-11-11
Authors: Kevin Wu
Published: 2026-11-11

react-i18next renders translations you write into a JSON file for each language and namespace. gt-react renders copy written inline in your components, and General Translation generates the translations with [`npx gt translate`](/docs/cli/reference/commands/translate). A migration replaces each `t('key')` lookup with the source text it points to, one component at a time, while both libraries run side by side.

You don&#39;t have to migrate to use General Translation. The `gt` CLI detects `i18next` in your `package.json` and translates its JSON files with i18next&#39;s `{{variable}}` syntax intact. Migrate when you want copy beside the JSX that renders it, without naming keys or keeping per-language files in sync by hand.

## How do react-i18next APIs map to gt-react?

| react-i18next                                  | gt-react                                                                                                                                                                                                      |
| ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `i18n.init({ lng, supportedLngs, resources })` | `gt.config.json` (`defaultLocale`, `locales`) and [`initializeGTSPA`](/docs/react/reference/config#initialize-spa) in the entry file                                                                          |
| `I18nextProvider` / `initReactI18next`         | No provider in a single-page app (SPA); server-rendered apps use [`initializeGT`](/docs/react/reference/config#initialize) with [`GTProvider`](/docs/react/reference/components/gt-provider)                  |
| `<Trans i18nKey="..." components={...}>`       | [`<T>`](/docs/react/reference/components/t) wrapping the real JSX, with links and markup written inline                                                                                                       |
| `t('key')` for strings                         | [`useGT`](/docs/react/reference/hooks/use-gt) with the source string, or [`useTranslations`](/docs/react/reference/hooks/use-translations) to keep keys                                                       |
| `{{name}}` interpolation                       | [`<Var>`](/docs/react/reference/components/var) in JSX; `{name}` in strings                                                                                                                                   |
| `key_one` / `key_other` with `count`           | [`<Plural>`](/docs/react/reference/components/plural) in JSX; ICU `{n, plural, ...}` in strings                                                                                                               |
| `useTranslation('ns')` or `keyPrefix`          | `useTranslations('ns')` scopes lookups under a prefix                                                                                                                                                         |
| `{{val, number}}` and other formatters         | [`<Num>`](/docs/react/reference/components/num), [`<Currency>`](/docs/react/reference/components/currency), and [`<DateTime>`](/docs/react/reference/components/datetime)                                     |
| `i18n.changeLanguage()` / `i18n.language`      | [`useSetLocale`](/docs/react/reference/hooks/use-set-locale) / [`useLocale`](/docs/react/reference/hooks/use-locale), or the [`<LocaleSelector>`](/docs/react/reference/components/locale-selector) component |
| `i18next-browser-languagedetector`             | Built in: the `generaltranslation.locale` cookie, then the browser&#39;s languages                                                                                                                            |
| `fallbackLng`                                  | A missing translation renders the source text                                                                                                                                                                 |

## Step 1: Install gt-react alongside react-i18next

Follow the [React SPA Quickstart](/docs/react/react-spa-quickstart) for a Vite or webpack app, or the [React Quickstart](/docs/react/react-quickstart) for a server-rendered one. In an SPA, the set-up is:

1. Install `gt-react` and the `gt` CLI as a dev dependency.
2. Create `gt.config.json` with the same source locale and target locales as your i18next config.
3. Add a [`loadTranslations`](/docs/react/reference/functions/load-translations) function, and call [`initializeGTSPA`](/docs/react/reference/config#initialize-spa) in an entry file that imports the rest of the app only after translations load.
4. Sign in with [`npx gt login`](/docs/cli/reference/commands/login) and set your project ID.

Keep `i18n.init` and your JSON files in place. Nothing changes for users until you move a component. If `gt.config.json` lists your i18next files under `files`, remove that entry so each string has one translation owner: the JSON keeps its translations for unmigrated components, and new or changed copy goes inline in gt-react.

## Step 2: Keep one active locale

Two libraries can disagree about the user&#39;s language. Pick gt-react as the switcher, and have i18next read the same cookie:

```ts title="src/i18n.ts"
import i18n from 'i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import { initReactI18next } from 'react-i18next';

i18n
  .use(LanguageDetector)
  .use(initReactI18next)
  .init({
    // ...your existing options
    detection: {
      order: ['cookie', 'navigator'],
      lookupCookie: 'generaltranslation.locale', // the cookie gt-react writes
      caches: [],
    },
  });
```

Replace your old language menu with gt-react&#39;s [`<LocaleSelector>`](/docs/react/reference/components/locale-selector) or a control that calls [`useSetLocale`](/docs/react/reference/hooks/use-set-locale). In an SPA, switching writes the cookie and reloads the page, so both libraries start in the new language. Test navigation between migrated and unmigrated screens before moving on.

## Step 3: Move one component at a time

Copy the source-language text from your JSON into the component, and keep variables as variables. Before:

```json title="public/locales/en/translation.json"
{
  "inbox": "Hi {{name}}, <link>open your inbox</link>.",
  "messages_one": "You have {{count}} message",
  "messages_other": "You have {{count}} messages"
}
```

```tsx title="src/Inbox.tsx"
import { Trans, useTranslation } from 'react-i18next';

export function Inbox({ name, count }: { name: string; count: number }) {
  const { t } = useTranslation();
  return (
    <>
      <Trans i18nKey="inbox" values={{ name }} components={{ link: <a href="/inbox" /> }} />
      <p>{t('messages', { count })}</p>
    </>
  );
}
```

After:

```tsx title="src/Inbox.tsx"
import { T, Var, Plural, Num } from 'gt-react';

export function Inbox({ name, count }: { name: string; count: number }) {
  return (
    <T>
      Hi <Var>{name}</Var>, <a href="/inbox">open your inbox</a>.
      <p>
        <Plural
          n={count}
          singular={<>You have <Num>{count}</Num> message</>}
          plural={<>You have <Num>{count}</Num> messages</>}
        />
      </p>
    </T>
  );
}
```

The link stays a real `<a>` element instead of a numbered placeholder. General Translation translates the whole block with its structure as context, and from the `singular` and `plural` you write, it generates the forms each target language needs, such as Polish&#39;s `few` and `many` forms. [`<Plural>`](/docs/react/reference/components/plural) then picks the right one for each count.

For strings outside JSX, such as placeholders and toasts, use [`useGT`](/docs/react/reference/hooks/use-gt). Variables use single braces, and you can add context for ambiguous words:

```tsx
import { useGT } from 'gt-react';

function Search({ count }: { count: number }) {
  const gt = useGT();
  return <input placeholder={gt('Search {count} projects', { count })} />;
}

// In an event handler, with a hint for the translator:
toast(gt('Saved {fileName}', { fileName, $context: 'toast after saving a file' }));
```

The first argument must be a static string, not a template literal or concatenation. After each batch of components, run [`npx gt translate`](/docs/cli/reference/commands/translate) and check every locale.

## Can you keep your translation keys?

Yes. Move your keys into a single gt-react [dictionary](/docs/react/guides/translating-with-dictionaries) and swap `useTranslation` for [`useTranslations`](/docs/react/reference/hooks/use-translations). The lookup call stays the same:

```tsx
// Before
import { useTranslation } from 'react-i18next';
const { t } = useTranslation();

// After
import { useTranslations } from 'gt-react';
const t = useTranslations();

return <h1>{t('home.welcome', { name })}</h1>;
```

Dictionary entries use ICU MessageFormat, so the JSON needs three changes:

* `{{name}}` becomes `{name}`.
* Plural keys merge into one entry: `"messages": "You have {count, plural, one {# message} other {# messages}}"`.
* Namespace files merge into one `dictionary.json`, with each namespace as a top-level key. `useTranslation('settings')` becomes `useTranslations('settings')`.

[`npx gt validate`](/docs/cli/reference/commands/validate) warns about entries that aren&#39;t valid ICU. Unlike i18next, which returns the key when an entry is missing, [`useTranslations`](/docs/react/reference/hooks/use-translations) throws, so keep ids and entries in sync.

## What happens to your existing translations?

Components you haven&#39;t moved keep rendering their reviewed react-i18next translations, so a staged migration changes only the screens you touch. Copy you move into [`<T>`](/docs/react/reference/components/t), [`useGT`](/docs/react/reference/hooks/use-gt), or a gt-react dictionary gets new translations from [`npx gt translate`](/docs/cli/reference/commands/translate), with [`<T>`](/docs/react/reference/components/t)&#39;s JSX structure and any context you add. A [Context Group](/docs/platform/dashboard/guides/defining-context-for-translations) Glossary and Custom Prompts keep your terminology and tone. Reviewers can then correct any wording in the [Dashboard](/docs/platform/dashboard/guides/reviewing-translations).

If every reviewed translation must stay exactly as it is, keep react-i18next and let General Translation translate its JSON files instead. (See [General Translation vs. i18next](/kb/general-translation-vs-i18next).)

## Step 4: Remove react-i18next

When a search for `useTranslation`, `<Trans`, and `i18next` returns nothing, remove the `i18n.init` file, the JSON files, the language detector, and the `i18next` and `react-i18next` dependencies. Then add [`npx gt translate`](/docs/cli/reference/commands/translate) to your build or [CI](/kb/translations-in-ci) so new copy ships translated in every release.

## Common questions

### Do I have to migrate everything at once?

No. Both libraries can render on the same page as long as they read the same locale. Move one route or feature at a time, and remove i18next after its last consumer is gone.

### Does gt-react support server-side rendering?

Yes. Server-rendered React apps use [`initializeGT`](/docs/react/reference/config#initialize) with [`GTProvider`](/docs/react/reference/components/gt-provider) instead of [`initializeGTSPA`](/docs/react/reference/config#initialize-spa). See the [React Quickstart](/docs/react/react-quickstart), or the [Next.js Quickstart](/docs/react/nextjs-quickstart) if you use `gt-next`.

### Is gt-react free?

gt-react is MIT-licensed and renders bundled translations without an account. Generating translations with General Translation is [usage-based](https://generaltranslation.com/pricing).

## Key takeaways

* Run gt-react next to react-i18next, point i18next&#39;s language detector at the `generaltranslation.locale` cookie, and switch languages with gt-react.
* Replace `<Trans>` and key lookups with inline copy in [`<T>`](/docs/react/reference/components/t) and [`useGT`](/docs/react/reference/hooks/use-gt); `{{var}}` becomes [`<Var>`](/docs/react/reference/components/var) or `{var}`, and plural keys become [`<Plural>`](/docs/react/reference/components/plural) with `singular` and `plural`.
* Unmoved components keep their reviewed translations; moved copy is translated with its JSX as context and reviewed in the Dashboard.
* To keep keys, convert your JSON to one ICU dictionary and use [`useTranslations`](/docs/react/reference/hooks/use-translations).
* Remove i18next only when nothing reads it, then run [`npx gt translate`](/docs/cli/reference/commands/translate) in CI.

## Related questions

- [i18next alternatives: General Translation vs. i18next](https://generaltranslation.com/en-GB/kb/general-translation-vs-i18next.mdx): Choose General Translation to keep copy inline in React components and generate translations with its JSX structure and product context. You can also keep i18next and use General Translation to translate existing catalogues. i18next supplies message rendering and plural selection; translation generation comes from a separate service.
- [How do I translate a React app?](https://generaltranslation.com/en-GB/kb/translate-react-app.mdx): Translating a React app means internationalising 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.
- [How do I translate strings outside JSX?](https://generaltranslation.com/en-GB/kb/translate-strings-outside-jsx.mdx): Pass each string that isn't a JSX child, such as a placeholder or a module-scope constant, through a translation function where it renders. General Translation's useGT hook returns that function, and msg with useMessages translates constants defined outside a component.
- [How do I handle plurals in React?](https://generaltranslation.com/en-GB/kb/how-to-handle-plurals-in-react.mdx): Handle plurals in React by giving each language one sentence per plural form and picking it with that language's plural rules, since English has 2 forms, Russian has 4 and Arabic has 6. General Translation's `<Plural>` component in gt-react and gt-next takes your source forms, and General Translation generates the forms each target language needs.
- [How do I keep translations up to date on every deploy?](https://generaltranslation.com/en-GB/kb/translations-in-ci.mdx): Run translation as a pipeline step before each production build, so new copy ships translated with the deploy that adds it. General Translation's gt CLI does this with npx gt translate, using a project API key and project ID stored as CI secrets.

## Previous and next questions

- Previous question: [How do I migrate from next-intl to gt-next?](https://generaltranslation.com/en-GB/kb/migrate-from-next-intl.mdx)
- Next question: [How do I migrate from vue-i18n to gt-vue?](https://generaltranslation.com/en-GB/kb/migrate-from-vue-i18n.mdx)

## Sitemap

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