# General Translation Knowledge Base: How do I migrate from vue-i18n to gt-vue?
URL: https://generaltranslation.com/en-US/kb/migrate-from-vue-i18n.mdx
Docs index: https://generaltranslation.com/llms.txt
Description: Install gt-vue next to vue-i18n, sync the active locale, and move one component at a time from $t keys to inline copy in `<T>` and useGT. General Translation translates the moved copy with its template structure as context, and your team can correct it in the Dashboard. Unmoved components keep their reviewed vue-i18n messages.
Category: Internationalization
Last updated: 2026-11-11
Authors: Kevin Wu
Published: 2026-11-11

vue-i18n renders translations you write into a message file for each language. gt-vue renders copy written inline in your templates, 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 plugins run in the same app.

## How do vue-i18n APIs map to gt-vue?

| vue-i18n | gt-vue |
| --- | --- |
| `createI18n({ locale, fallbackLocale, messages })` | [`createGT`](/docs/vue/reference/functions/create-gt)`({ defaultLocale, loadTranslations })`, with locales in `gt.config.json` |
| `app.use(i18n)` | `app.use(gt)` |
| `$t('key')` in templates | [`<T>`](/docs/vue/reference/components/t) around the copy, or a string from [`useGT`](/docs/vue/reference/composables/use-gt) |
| `t('key')` in `<script setup>` | `const gt = useGT()`, then `gt('Inbox')` inside a `computed` |
| `{name}` named interpolation | [`<Var>`](/docs/vue/reference/components/var) inside [`<T>`](/docs/vue/reference/components/t) |
| `<i18n-t keypath="...">` with slots | [`<T>`](/docs/vue/reference/components/t) wrapping the real markup |
| `'no apples \| one apple \| {count} apples'` | [`<Plural>`](/docs/vue/reference/components/plural) with `#zero`, `#singular`, and `#plural` slots |
| `n()` and `d()` with named formats | [`<Num>`](/docs/vue/reference/components/num), [`<Currency>`](/docs/vue/reference/components/currency), and [`<DateTime>`](/docs/vue/reference/components/datetime), with `Intl` options inline |
| `locale.value = 'fr'` | [`useSetLocale`](/docs/vue/reference/composables/use-set-locale)`()('fr')`; read it with [`useLocale`](/docs/vue/reference/composables/use-locale) |
| Lazy loading with `setLocaleMessage` | The `loadTranslations(locale)` option loads and caches each locale |
| `fallbackLocale` | `defaultLocale`; a missing translation renders the source text |

Message keys, SFC `<i18n>` custom blocks, and linked messages (`@:key`) all become inline copy: move each message's text into the template that renders it, and write linked messages out in full.

## Step 1: Install gt-vue alongside vue-i18n

gt-vue (currently 0.x) supports Vue 3.3 and later. Follow the [Vue Quickstart](/docs/vue/quickstart). In short:

1. Install `gt-vue` and the `gt` CLI as a dev dependency.
2. Create `gt.config.json` with the same source locale and target locales as your vue-i18n config, and an output path such as `src/_gt/[locale].json`.
3. Add a `loadTranslations` function and install the plugin next to vue-i18n:

   ```ts title="src/main.ts"
   import { createApp } from 'vue';
   import { createGT, type LoadTranslations } from 'gt-vue';
   import App from './App.vue';
   import i18n from './i18n'; // your existing createI18n instance

   const loadTranslations: LoadTranslations = async (locale) => {
     try {
       return (await import(`./_gt/${locale}.json`)).default;
     } catch {
       return {};
     }
   };

   createApp(App)
     .use(i18n)
     .use(createGT({ defaultLocale: 'en', loadTranslations }))
     .mount('#app');
   ```

4. Sign in with [`npx gt login`](/docs/cli/reference/commands/login) and set your project ID.

gt-vue needs no Vite plugin: the CLI scans `.vue` files for [`<T>`](/docs/vue/reference/components/t) and [`useGT`](/docs/vue/reference/composables/use-gt) when you run [`npx gt translate`](/docs/cli/reference/commands/translate).

## Step 2: Keep one active locale

Use gt-vue as the language switcher, and copy its locale into vue-i18n so unmigrated components follow along:

```vue title="src/App.vue"
<script setup lang="ts">
import { watchEffect } from 'vue';
import { useI18n } from 'vue-i18n';
import { useLocale } from 'gt-vue';

const gtLocale = useLocale();
const { locale } = useI18n({ useScope: 'global' });

watchEffect(() => {
  locale.value = gtLocale.value;
});
</script>
```

Replace your language menu with a control that calls [`useSetLocale`](/docs/vue/reference/composables/use-set-locale). With [`createGT`](/docs/vue/reference/functions/create-gt), switching loads the new locale's translations and rerenders without a page reload, and gt-vue stores the choice in the `generaltranslation.locale` cookie.

## Step 3: Move one component at a time

Copy the source-language text from your message file into the template. Before:

```json title="src/locales/en.json"
{
  "greeting": "Hello, {name}!",
  "unread": "You have no messages | You have {count} message | You have {count} messages",
  "inbox": "Inbox"
}
```

```vue title="src/components/InboxHeader.vue"
<script setup lang="ts">
import { useI18n } from 'vue-i18n';
defineProps<{ name: string; count: number }>();
const { t } = useI18n();
</script>

<template>
  <h1>{{ t('inbox') }}</h1>
  <p>{{ t('greeting', { name }) }}</p>
  <p>{{ t('unread', count) }}</p>
</template>
```

After:

```vue title="src/components/InboxHeader.vue"
<script setup lang="ts">
import { computed } from 'vue';
import { T, Var, Num, Plural, useGT } from 'gt-vue';
defineProps<{ name: string; count: number }>();

const gt = useGT();
const title = computed(() => gt('Inbox')); // computed keeps it reactive to locale changes
</script>

<template>
  <h1>{{ title }}</h1>
  <T>
    <p>Hello, <Var>{{ name }}</Var>!</p>
    <p>
      <Plural :n="count">
        <template #zero>You have no messages</template>
        <template #singular>You have <Num :value="count" /> message</template>
        <template #plural>You have <Num :value="count" /> messages</template>
      </Plural>
    </p>
  </T>
</template>
```

From the `singular` and `plural` slots, General Translation generates the forms each target language needs, such as Russian's `few` and `many`, and [`<Plural>`](/docs/vue/reference/components/plural) picks one with `Intl.PluralRules`; vue-i18n's pipe syntax needs a custom pluralization rule for those languages. The `zero` slot keeps your wording for an empty inbox. Use [`useGT`](/docs/vue/reference/composables/use-gt) for static strings such as titles and placeholders, and put any sentence with a runtime value in [`<T>`](/docs/vue/reference/components/t) with [`<Var>`](/docs/vue/reference/components/var). After each batch of components, run [`npx gt translate`](/docs/cli/reference/commands/translate) and check every locale.

## What happens to your existing translations?

Components you haven't moved keep rendering their reviewed vue-i18n messages, so a staged migration changes only the screens you touch. gt-vue identifies translations by their source content rather than by message key, so copy you move into [`<T>`](/docs/vue/reference/components/t) or [`useGT`](/docs/vue/reference/composables/use-gt) gets new translations from [`npx gt translate`](/docs/cli/reference/commands/translate), generated with the surrounding template as context. 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).

## Step 4: Remove vue-i18n

When a search for `$t(`, `useI18n`, `<i18n-t`, and `vue-i18n` returns nothing, remove the `createI18n` setup, the message files, the locale sync, and the `vue-i18n` dependency. 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

### Does gt-vue support Nuxt?

gt-vue has no Nuxt module, and it doesn't provide localized routes, hreflang tags, or browser language detection the way `@nuxtjs/i18n` does. The CLI does scan Nuxt's `pages/` and `layouts/` folders, and [`createGT`](/docs/vue/reference/functions/create-gt) can render on the server when you create one plugin per request.

### Does gt-vue support message keys?

No. gt-vue translates copy written inline in [`<T>`](/docs/vue/reference/components/t) and [`useGT`](/docs/vue/reference/composables/use-gt). If keys are central to your workflow, keep vue-i18n.

### Can I migrate one component at a time?

Yes. Both plugins can run in the same app as long as they share the active locale. Remove vue-i18n after its last consumer is gone.

### Is gt-vue free?

gt-vue 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-vue next to vue-i18n, switch languages with gt-vue, and copy its locale into vue-i18n until the migration ends.
- Replace `$t` keys with inline copy: [`<T>`](/docs/vue/reference/components/t) with [`<Var>`](/docs/vue/reference/components/var) for templates, [`<Plural>`](/docs/vue/reference/components/plural) `singular` and `plural` slots for pipe plurals, and [`useGT`](/docs/vue/reference/composables/use-gt) in a `computed` for static script strings.
- Unmoved components keep their reviewed vue-i18n messages; moved copy is translated with its template as context and reviewed in the Dashboard. Keep vue-i18n if message keys or `@nuxtjs/i18n` routing are central to your app.
- Remove vue-i18n when nothing reads it, then run [`npx gt translate`](/docs/cli/reference/commands/translate) in CI.

## Related questions

- [General Translation vs. vue-i18n: which should you use?](https://generaltranslation.com/en-US/kb/general-translation-vs-vue-i18n.mdx): General Translation lets Vue teams write source copy directly in templates and generate translations with the CLI. Its gt-vue plugin renders that content with locale-aware plural and number formatting. Choose it to remove keyed source-file maintenance; vue-i18n remains a fit for teams using Nuxt's i18n module and an established catalog workflow.
- [How do I translate a Vue app?](https://generaltranslation.com/en-US/kb/translate-vue-app.mdx): Translating a Vue app means putting each template string behind a lookup for the active language and translating it into every target language. General Translation's gt-vue plugin translates content wrapped in the `<T>` component, and npx gt translate writes one catalog per target language.
- [How do I keep translations up to date on every deploy?](https://generaltranslation.com/en-US/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 react-i18next to gt-react?](https://generaltranslation.com/en-US/kb/migrate-from-react-i18next.mdx)
- Next question: [AI coding agent vs. translation pipeline: which should automate your localization?](https://generaltranslation.com/en-US/kb/ai-coding-agent-vs-translation-pipeline.mdx)

## Sitemap

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