# General Translation Knowledge Base: How do I migrate from Lokalise to General Translation?
URL: https://generaltranslation.com/en-US/kb/migrate-from-lokalise.mdx
Docs index: https://generaltranslation.com/llms.txt
Description: 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.
Category: Workflow Tools
Last updated: 2026-11-12
Authors: Kevin Wu
Published: 2026-11-12

Lokalise stores your strings as keys in a project, generates resource files for each platform on export, and syncs them with your repository. Moving to General Translation keeps the resource 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'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 glossary becomes a Context Group that your app, docs, and other projects can share.

## What carries over from Lokalise?

| In Lokalise | In General Translation |
| --- | --- |
| Keys exported as files for each platform | The same files, listed under `files` in `gt.config.json` |
| Translations | Imported with [`npx gt upload`](/docs/cli/reference/commands/upload) and kept until their source changes |
| Glossary | A [Context Group](/docs/platform/dashboard/guides/defining-context-for-translations) Glossary, imported from CSV |
| Style guides for Pro AI | [Custom Prompts](/docs/platform/dashboard/guides/defining-context-for-translations), globally or per locale |
| Key descriptions and character limits | Comments in iOS and Android files, which General Translation reads as context, or a [keyed metadata](/docs/cli/reference/keyed-metadata) file for JSON and YAML |
| Translation memory | Your uploaded translations: unchanged strings keep them, and only new and changed strings are translated. TMX files aren't imported |
| GitHub app, auto-pull, and GitHub Actions | [`npx gt translate`](/docs/cli/reference/commands/translate) in CI, or [Locadex](/docs/platform/locadex/quickstart) pull requests |
| Review tasks | Editing in the [Dashboard](/docs/platform/dashboard/guides/reviewing-translations), with approval on the Enterprise plan |
| Over-the-air SDKs for iOS, Android, and Flutter | Translations ship in your resource 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't read XLIFF, ARB, Java `.properties`, or CSV and XLSX exports, so choose a supported format when you download.

## Step 1: Export one file per language

Lokalise doesn't keep your original files; it builds them from keys on every export. If your repository already holds files Lokalise synced, export with the same settings so the output matches, and diff it against the repository before importing.

With the [`lokalise2` CLI](https://github.com/lokalise/lokalise-cli-2-go/blob/master/docs/lokalise2_file_download.md), a web app's JSON export looks like this:

```bash
lokalise2 --token "$LOKALISE_TOKEN" --project-id "$LOKALISE_PROJECT_ID" file download \
  --format json \
  --original-filenames=false \
  --bundle-structure "locales/%LANG_ISO%/common.json" \
  --unzip-to . \
  --export-empty-as skip \
  --filter-data nonhidden \
  --async
```

Four settings matter for a clean import:

- **`--export-empty-as skip`.** By default, Lokalise exports untranslated keys as empty strings, and `base` fills them with source text. Either would be imported as a translation. `skip` leaves them out.
- **Plural and placeholder formats.** Match what your library reads: `--plural-format i18next_v4` for i18next's `_one` and `_other` keys, or `--plural-format icu --placeholder-format icu` for next-intl. (See Lokalise's [plural and placeholder formats](https://developers.lokalise.com/reference/api-plurals-and-placeholders).)
- **Platforms.** Each key belongs to one or more platforms, and an export includes only the keys on its format's platform. Run one export per platform: `--format strings` and `stringsdict` for iOS, and `--format xml` for Android.
- **`--async`.** Lokalise's synchronous download serves only projects under 10,000 key-language pairs; larger projects need the asynchronous one.

Without the CLI, open the project's **Download** page, choose the format, set **Empty translations** to skip, and click **Build and download**. Downloading needs the **Download files** contributor permission.

Check the locale codes in the folder names. Lokalise uses its own language codes, such as `en_GB`, which the CLI matches against your `locales` list exactly. Override a code by clicking it on the **Download** page or with `--language-mapping`, or list the codes as exported in `gt.config.json`.

## Step 2: Stop Lokalise's sync

Before General Translation writes any files, turn off every path that writes translations into your repository:

- Delete the Lokalise auto-pull webhook from your GitHub repository's settings.
- Stop using the GitHub trigger on downloads, which opens pull requests from a new branch.
- Remove or disable workflows that use Lokalise's push and pull GitHub Actions.

The GitLab, Bitbucket, and Azure DevOps apps work the same way. If two tools write the same files, whichever writes last wins.

## Step 3: Point the CLI at the files

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

```json title="gt.config.json"
{
  "defaultLocale": "en",
  "locales": ["de", "fr", "pt-BR"],
  "files": {
    "json": { "include": ["locales/[locale]/*.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 > Write**, **Translation queue > Enabled**, and **Context > Write** permissions.

## Step 4: Bring over your glossary and key context

On Lokalise's [Glossary](https://docs.lokalise.com/en/articles/1400629-glossary) page, use **More > Download CSV** to export every term with its translations. The file is semicolon-separated and has a column per language, such as `de_DE`. Keep `term`, rename `description` to `definition` and each language column to `translation_<locale>`, such as `translation_de`, delete the other columns, and save the file comma-separated. General Translation'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 tell the translator what a string means. iOS `.strings` and Android `strings.xml` exports include them as comments by default, and General Translation sends those comments as context. For JSON, get each key's description and character limit from `lokalise2 key list`, or from a [`json_structured`](https://docs.lokalise.com/en/articles/3229161-structured-json) export of your source language saved outside the folders in `gt.config.json`, and write them to a [keyed metadata](/docs/cli/reference/keyed-metadata) file next to the source file:

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

Recreate your Pro AI style guides as [Custom Prompts](/docs/platform/dashboard/guides/defining-context-for-translations). If your plan includes translation memory management, a team admin can keep an archive with **Download as TMX file** under **Team settings > Translation memory**.

## 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'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 exported files. General Translation translates keys Lokalise hadn't translated the next time their source file changes. To ship them sooner, finish them in Lokalise before you export.

## 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'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 Lokalise?

Stay when native mobile apps depend on over-the-air string updates, when designers work from the Figma plugin and screenshots, when you order professional translation inside the same tool, or when your pipeline exchanges XLIFF or ARB files. (See [General Translation vs. Lokalise](/kb/general-translation-vs-lokalise).)

## Common questions

### Will I lose my reviewed translations?

No, if you skip empty translations on export 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).

### What happens to screenshots?

They stay in Lokalise. To keep what they told translators, describe where each string appears in its per-key context (Step 4).

## Key takeaways

- Export one file per language and platform with `--export-empty-as skip`, matching the plural format your app already reads.
- Remove Lokalise's webhook, triggers, and Actions before General Translation writes the same files.
- Import the glossary into a Context Group and carry key descriptions 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

- [Lokalise alternatives: General Translation vs. Lokalise](https://generaltranslation.com/en-US/kb/general-translation-vs-lokalise.mdx): General Translation is a strong Lokalise alternative for teams that want readable copy in JSX and translation built into development. Lokalise is built around shared keys, design-tool integrations, and mobile over-the-air delivery. General Translation generates translations from the app's marked content and supports review in its Dashboard or in GitHub pull requests.
- [How do I migrate from Crowdin to General Translation?](https://generaltranslation.com/en-US/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 migrate from Phrase to General Translation?](https://generaltranslation.com/en-US/kb/migrate-from-phrase.mdx): 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.
- [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.
- [What is translation memory, and do you still need it with AI?](https://generaltranslation.com/en-US/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 Crowdin to General Translation?](https://generaltranslation.com/en-US/kb/migrate-from-crowdin.mdx)
- Next question: [How do I migrate from Phrase to General Translation?](https://generaltranslation.com/en-US/kb/migrate-from-phrase.mdx)

## Sitemap

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