# General Translation Knowledge Base: How do I migrate from Phrase to General Translation?
URL: https://generaltranslation.com/en-GB/kb/migrate-from-phrase.mdx
Docs index: https://generaltranslation.com/llms.txt
Description: Keep your locale files, i18n library, and verified translations. Pull from Phrase Strings without untranslated keys or fallbacks, deactivate Repo Sync, and import the files with npx gt upload before the first npx gt translate. General Translation then translates new and changed strings in your build, with your term base in a Context Group and key descriptions as per-key context.
Category: Workflow Tools
Last updated: 2026-11-12
Authors: Kevin Wu
Published: 2026-11-12

Phrase Strings, Phrase&#39;s software localisation product, stores your strings as keys in a project, generates locale files on download, and syncs them with your repository. Moving to General Translation keeps the locale files your app already reads: the `gt` CLI imports them with your existing translations, then translates new and changed strings in your build or CI. Your i18n library and keys don&#39;t change; moving copy inline is a separate, optional step (see [migrating from react-i18next](/kb/migrate-from-react-i18next) or [from next-intl](/kb/migrate-from-next-intl)). Your term base becomes a Context Group that your app, docs, and other projects can share. This guide covers Phrase Strings; documents translated in Phrase TMS follow a different workflow.

## What carries over from Phrase Strings?

| In Phrase Strings                                                                   | In General Translation                                                                                                                  |
| ----------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `.phrase.yml` pull targets                                                          | `files` in `gt.config.json`, with `[locale]` in place of `<locale_name>`                                                                |
| Translations                                                                        | Imported with [`npx gt upload`](/docs/cli/reference/commands/upload) and kept until their source changes                                |
| Term base                                                                           | A [Context Group](/docs/platform/dashboard/guides/defining-context-for-translations) Glossary, imported from CSV                        |
| Style guides                                                                        | [Custom Prompts](/docs/platform/dashboard/guides/defining-context-for-translations), globally or per locale                             |
| Key descriptions and character limits                                               | A [keyed metadata](/docs/cli/reference/keyed-metadata) file with `context` and `maxChars` per key, for JSON and YAML                    |
| Translation memory                                                                  | Your uploaded translations: unchanged strings keep them, and only new and changed strings are translated. TMX files aren&#39;t imported |
| Repo Sync                                                                           | [`npx gt translate`](/docs/cli/reference/commands/translate) in CI, or [Locadex](/docs/platform/locadex/quickstart) pull requests       |
| Translation verification and review                                                 | Editing in the [Dashboard](/docs/platform/dashboard/guides/reviewing-translations), with approval on the Enterprise plan                |
| Over-the-air (OTA) SDKs for iOS, Android, React Native, Flutter, i18next, and Rails | Translations ship in your locale files with each app release                                                                            |

The CLI reads JSON, YAML, `.pot` and `.po`, iOS `.strings`, `.stringsdict`, and `.xcstrings`, Android `strings.xml`, `.resx`, HTML, Markdown and MDX, and more. (See the [supported formats](/docs/cli/reference/config#files).) It doesn&#39;t read XLIFF, ARB, Java `.properties`, or CSV and XLSX exports, so choose a supported format when you pull.

## Step 1: Pull one file per locale

Phrase generates files from keys on every download. If your repository already holds files Phrase synced, pull with the same `.phrase.yml` targets so the output matches, and diff it against the repository before importing.

A pull target for a JSON app looks like this:

```yaml title=".phrase.yml"
phrase:
  project_id: "YOUR_PROJECT_ID"
  file_format: nested_json
  pull:
    targets:
      - file: ./locales/<locale_name>/app.json
        params:
          file_format: nested_json
          include_empty_translations: false
```

Then run [`phrase pull`](https://developers.phrase.com/en/developer-tools/strings-cli) with an access token that has the `read` scope. Leave off `--branch` to pull from the main branch. If your account is in Phrase&#39;s US data centre, add `host: https://api.us.app.phrase.com/v2` under `phrase:` in `.phrase.yml`.

Three settings matter for a clean import:

* **Untranslated keys.** `include_empty_translations: false`, the CLI&#39;s default, leaves untranslated keys out. Don&#39;t set `fallback_locale_id` or `use_locale_fallback`, and check that the project setting **Use fallback for unverified translations** is off. Each fills gaps with another locale&#39;s text, which would be imported as a translation.
* **File format.** Use the format your library already reads: `nested_json` or `simple_json`, `i18next_4` for i18next, `yml` for Rails, `strings` and `stringsdict` for iOS, `strings_catalog` for `.xcstrings`, and `xml` for Android. Phrase&#39;s `json` format is Chrome extension JSON, not general-purpose JSON. (See Phrase&#39;s [supported file formats](https://support.phrase.com/hc/en-us/articles/9652464547740).)
* **Locale names.** `<locale_name>` expands to the name you gave each locale in Phrase, such as `de-AT`. The `gt` CLI matches folder names against your `locales` list exactly, so rename the locales in Phrase, map them with `locale_mapping` under `phrase:` in `.phrase.yml` (Phrase CLI 2.59.0 or later), or list the names as pulled in `gt.config.json`.

Without the CLI, open the project&#39;s **Languages** tab and click **Download** to get several locales at once, leaving **Include empty translations** unticked.

## Step 2: Deactivate Repo Sync and OTA

Phrase&#39;s [Repo Sync](https://support.phrase.com/hc/en-us/articles/5784125562012-GitHub-Strings), set up under **Automations &gt; Repo Syncs**, can import on push and exports translations as pull requests, from a `phrase-translations` branch by default. Before General Translation writes any files, deactivate each sync so it stops importing and exporting, close any open Phrase pull requests, and remove `phrase push` and `phrase pull` steps from your CI. If two tools write the same files, whichever writes last wins.

If your mobile apps use Phrase OTA, installed apps keep receiving new OTA releases. Stop creating releases, or delete the distribution, so they stop receiving updates, and remove the SDK in a later app release.

## Step 3: Point the CLI at the files

Install the `gt` CLI and describe the pulled files in `gt.config.json`:

```json title="gt.config.json"
{
  "defaultLocale": "en",
  "locales": ["de", "fr", "pt-BR"],
  "files": {
    "json": { "include": ["locales/[locale]/app.json"] }
  }
}
```

The CLI reads i18next and next-intl message syntax from JSON when it finds `i18next` or `next-intl` in your `package.json`, and treats other JSON values as plain strings. (See [JSON files](/docs/cli/reference/formats/json-files).)

Sign in with [`npx gt login`](/docs/cli/reference/commands/login) and set `GT_PROJECT_ID`, or set `projectId` in the config. In CI, use `GT_API_KEY` with a project API key that has **Files &gt; Write**, **Translation queue &gt; Enabled** and **Context &gt; Write** permissions.

## Step 4: Bring over your term base and key context

In Phrase Strings, open **Term bases**, then **More &gt; Download CSV**, choose the fields, and click **Download**. The file has `term`, `case_sensitive`, `translatable`, and `description` columns, plus one column per locale. (See [Uploading and downloading terms](https://support.phrase.com/hc/en-us/articles/10112307427740).) Rename `description` to `definition` and each locale column, such as `de`, to `translation_de`, and delete the `case_sensitive` and `translatable` columns. General Translation&#39;s importer reads that layout directly and interprets other layouts with AI. In the Dashboard, create a [Context Group](/docs/platform/dashboard/guides/defining-context-for-translations), click **Import**, choose the file, and assign the group to your project before the first translation run, so new translations use your terminology from the start.

Key descriptions and character limits tell the translator what a string means and how long it can be. Add a second pull target for your source locale with `file_format: strings_json`, saved outside the folders in `gt.config.json` (for example `./phrase-export/app.strings.json`). It includes each key&#39;s `description` and `max_characters_allowed`; write them to a [keyed metadata](/docs/cli/reference/keyed-metadata) file next to the source file:

```json title="locales/en/app.metadata.json"
{
  "checkout": {
    "save": { "context": "Button that saves the shipping address", "maxChars": 12 }
  }
}
```

Recreate your style guides as [Custom Prompts](/docs/platform/dashboard/guides/defining-context-for-translations). To keep an archive of your translation memory, download each language from the project as TMX.

## Step 5: Upload before you translate

Run [`npx gt upload`](/docs/cli/reference/commands/upload) first. It sends each source file and the matching translated file for every locale, and stores those translations as completed:

```bash
npx gt upload
npx gt translate
```

[`npx gt translate`](/docs/cli/reference/commands/translate) then translates only what&#39;s missing, such as a new locale or a file with no translation, and writes the results to the same paths. Running [`npx gt translate`](/docs/cli/reference/commands/translate) before [`npx gt upload`](/docs/cli/reference/commands/upload) generates new translations that overwrite the pulled files. General Translation translates keys Phrase hadn&#39;t translated the next time their source file changes. To ship them sooner, finish them in Phrase before you pull.

## Step 6: Translate new copy automatically

From now on, run [`npx gt translate`](/docs/cli/reference/commands/translate) in your [build or CI](/kb/translations-in-ci), or let [Locadex](/docs/platform/locadex/quickstart) open pull requests with translations. Commit `gt-lock.json`, which records the translations the CLI has already written so it doesn&#39;t download them again. If your team edits translated files in the repository, run [`npx gt save-local`](/docs/cli/reference/commands/save-local) before the next translation so those edits are kept.

## When should you stay on Phrase?

Stay when one platform must serve both software strings and a document translation programme in Phrase TMS, when native mobile apps depend on OTA updates, when you order human translation through Phrase, or when your pipeline exchanges XLIFF or ARB files. (See [General Translation vs. Phrase](/kb/general-translation-vs-phrase).)

## Common questions

### Will I lose my verified translations?

No, if you pull without fallbacks and run [`npx gt upload`](/docs/cli/reference/commands/upload) before [`npx gt translate`](/docs/cli/reference/commands/translate). Uploaded translations stay in place until their source text changes, and your team can correct any translation in the [Dashboard](/docs/platform/dashboard/guides/reviewing-translations).

## Key takeaways

* Pull one file per locale with `include_empty_translations: false` and no fallback locale, in the format your app already reads.
* Deactivate Repo Sync and OTA before General Translation writes the same files.
* Match Phrase locale names to the `locales` in `gt.config.json`, import the term base into a Context Group, and carry key descriptions and character limits over as per-key context before translating.
* Run [`npx gt upload`](/docs/cli/reference/commands/upload) before the first [`npx gt translate`](/docs/cli/reference/commands/translate), then translate new copy in your build or with Locadex.

## Related questions

- [Phrase alternatives: General Translation vs. Phrase](https://generaltranslation.com/en-GB/kb/general-translation-vs-phrase.mdx): General Translation is a strong Phrase Strings alternative when your app and documentation ship together. It generates translations from code and content files, applies shared terminology, and runs in your release workflow. Phrase combines software localisation with a document-focused TMS, vendor management, and a broad choice of translation engines.
- [How do I migrate from Lokalise to General Translation?](https://generaltranslation.com/en-GB/kb/migrate-from-lokalise.mdx): Keep your resource files, i18n library, and reviewed translations. Export from Lokalise without empty translations, turn off its GitHub sync, and import the files with npx gt upload before the first npx gt translate. General Translation then translates new and changed strings in your build, with your glossary in a Context Group and key descriptions as per-key context.
- [How do I migrate from Crowdin to General Translation?](https://generaltranslation.com/en-GB/kb/migrate-from-crowdin.mdx): Keep your resource files, i18n library, and reviewed translations. Turn on Skip untranslated strings, download your translations, stop Crowdin's Git integration from pushing translations, and import the files with npx gt upload before the first npx gt translate. General Translation then translates new and changed strings in your build, using your glossary from a Context Group.
- [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.
- [What is translation memory, and do you still need it with AI?](https://generaltranslation.com/en-GB/kb/what-is-translation-memory.mdx): Translation memory (TM) is a database of earlier source and target segment pairs that a translation tool searches for exact, in-context, and fuzzy matches. With AI, you still want approved wording reused and past translations available as context. General Translation reuses matching translations from earlier file versions and keeps team edits, without a separate TM to maintain.

## Previous and next questions

- Previous question: [How do I migrate from Lokalise to General Translation?](https://generaltranslation.com/en-GB/kb/migrate-from-lokalise.mdx)
- Next question: [What's the best continuous localisation tool for teams that ship daily?](https://generaltranslation.com/en-GB/kb/best-continuous-localization-tools.mdx)

## Sitemap

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