> ## Documentation Index
> Fetch the complete documentation index at: https://docs.textsetu.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Migrating from Crowdin

> Move a Crowdin project, its translation memory and its glossary into TextSetu with one command.

Your translations, translation memory and glossary are years of work. Moving them
should not be a week of exports, spreadsheets and re-imports.

```bash theme={null}
npx @textsetu/cli@latest migrate crowdin --from 123456 --dry-run
npx @textsetu/cli@latest migrate crowdin --from 123456
```

One command reads a Crowdin project through Crowdin's API and writes it into a
TextSetu project. It runs on your machine with your own Crowdin token, so no
third-party credential is ever stored by TextSetu, and nothing in Crowdin is
modified — the migration only reads.

<Note>
  **Run `--dry-run` first, always.** It reads both sides in full and prints
  exactly what would land: which languages get added, how many keys and TM
  segments and glossary terms, every key that had to be renamed, and every loss
  as a number. It writes nothing and exits `0`.
</Note>

***

## What moves

|                              |                                                                                                                              |
| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| **Strings and translations** | Every language at once, with plurals keeping all their CLDR forms and placeholders (`%s`, `%1$d`, `{count}`) byte-identical. |
| **Key context**              | Crowdin's string context becomes the TextSetu key description.                                                               |
| **Labels**                   | Become tags on the key.                                                                                                      |
| **Translation memory**       | Every segment and all of its language variants, with region subtags like `pt-BR` and `pt-PT` kept distinct.                  |
| **Glossary**                 | Terms with their status, part of speech, term type, definition, notes and grammatical gender.                                |

## Before you start

**In Crowdin**, create a personal access token under **Account Settings → API**.
It only needs read access. Note your project id — the number in the project URL.
On Crowdin Enterprise, note your organization name too, the `<name>` in
`<name>.crowdin.com`.

**In TextSetu:**

1. **Create the project** you are migrating into, and **set its source language**
   to match Crowdin's. The command adds Crowdin's source language if it is
   missing, but it cannot make it the project's source — do that first, or your
   source strings arrive as just another target language.
2. **Sign in** with `textsetu login`. A personal access token is required for the
   translation memory and glossary, because both belong to the organization; a
   project token can still do `--only translations`.
3. **Check your plan has head-room.** Keys, languages, translation memories and
   glossaries are all metered. An organization over its limits is read-only, and
   the run stops.

<Warning>
  **Decide your key shape before you run.** A Crowdin identifier is unique **per
  file**; a TextSetu key is unique **per project**. If `common.json` and
  `errors.json` both contain `title`, those are two Crowdin strings competing for
  one TextSetu key. The migration refuses and names them rather than silently
  letting one win. Either rename one in Crowdin, or run with `--on-collision
      prefix`, which namespaces every key by its file (`common.title`, `errors.title`).
</Warning>

## Migrating

1. **Set your tokens.** `export CROWDIN_TOKEN=…` for Crowdin (prefer the
   environment variable over the `--crowdin-token` flag, which leaves the secret
   in your shell history), and `textsetu login` for TextSetu.
2. **Dry-run**, and read the plan. Section [Reading the plan](#reading-the-plan)
   explains every line.
3. **Resolve anything it surfaced** — a key collision, a language spelled
   differently in the two tools (`--locale-map zh-CN=zh-Hans`), renames you would
   rather make yourself in Crowdin. Then dry-run again.
4. **Run it for real.** Languages are created first, then translations, then the
   translation memory, then the glossary.
5. **Verify.** `textsetu status` shows completeness per language — compare it with
   Crowdin's own. Then spot-check a plural key, a `pt-BR`/`pt-PT` pair and a
   string with placeholders.

### Choosing what to bring

| Flag                                            |                                                                                     |
| ----------------------------------------------- | ----------------------------------------------------------------------------------- |
| `--from <id>`                                   | The Crowdin project. Prompts at a terminal when omitted.                            |
| `--crowdin-tm <id>` / `--crowdin-glossary <id>` | Which TM and glossary to bring. Prompts at a terminal; otherwise lists the options. |
| `--only <part…>`                                | Any of `translations`, `tm`, `glossary`. Default: all three.                        |
| `--organization <name>`                         | Crowdin Enterprise.                                                                 |
| `--locale-map <from=to…>`                       | Map a Crowdin language onto a different TextSetu code. Repeatable.                  |
| `--on-collision prefix`                         | Namespace every key by its Crowdin file.                                            |
| `--dry-run`                                     | Print the plan and write nothing.                                                   |
| `--json`                                        | The same plan as a document, including the full rename list.                        |

## Reading the plan

```
crowdin  textsetu  action
───────  ────────  ─────────────
en       en        source exists
fr       fr        add
zh-CN    zh-Hans   add (--locale-map)

translations        10 key(s), 1 plural · 1 request(s), 2 KB
                    3 key(s) get a description or tags
                    ⚠ 10 identifiers renamed to a valid key
                    ⚠ 1 deliberately-empty translation(s) will arrive untranslated
                    1 max-length limit(s) noted in the key description
translation memory  "Product TM" → new TM · 4 entries · 7 language(s) · 1 request(s)
glossary            "Product Glossary" → new glossary · 3 term(s) · 3 language(s)

Dry run: nothing written. 18 TextSetu request(s).
```

| Line                                 | Meaning                                                                                  |
| ------------------------------------ | ---------------------------------------------------------------------------------------- |
| `source`                             | Crowdin's source language.                                                               |
| `add` / `exists`                     | Whether the language will be added to your TextSetu project.                             |
| `(--locale-map)`                     | This mapping came from your override, not from normalization.                            |
| `N key(s), M plural`                 | Keys to import, and how many carry plural forms.                                         |
| `N request(s), N KB`                 | How the upload is split against the 10 MB request limit. One request is the normal case. |
| `N key(s) get a description or tags` | Metadata only ever *fills* what is empty, so a second run usually shows `0`.             |
| `→ new TM` / `→ existing "…"`        | Whether a container is created, or reused by name.                                       |

## Warnings

Warnings never stop a run. Each names a loss, with a count, so you can decide
before anything is written.

| Warning                                                        | What it means                                                                                                                                         | What to do                                                                                                   |
| -------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| `N identifier(s) renamed to a valid key`                       | A TextSetu key segment allows letters, digits, `_` and `-`, so `user profile` becomes `user_profile`. `--on-collision prefix` counts as a rename too. | Nothing, or rename in Crowdin first. `--json` lists every pair.                                              |
| `N deliberately-empty translation(s) will arrive untranslated` | Crowdin can hold an empty string as a real translation; TextSetu treats empty as absent.                                                              | Accept it, or give those strings a real value.                                                               |
| `N max-length limit(s) noted in the key description`           | TextSetu has no max-length field, so Crowdin's limit is appended to the description as text.                                                          | Nothing. It is informational.                                                                                |
| `N segment(s) have no source-language text and are skipped`    | A TM segment with no variant in the source language has no lookup key.                                                                                | Check the TM's source language matches the project's.                                                        |
| `N extra synonym(s) dropped`                                   | Crowdin allows several terms per language in one concept; TextSetu keeps one.                                                                         | Nothing, or split the concept in Crowdin.                                                                    |
| `N concept(s) have no source-language term and are skipped`    | The same, for glossary concepts.                                                                                                                      | Check the glossary's source language.                                                                        |
| `The TextSetu project has no <lang> language…`                 | Your project's source language differs from Crowdin's.                                                                                                | Set the source language in project settings, then re-run.                                                    |
| `The TextSetu project requires approval…`                      | Imported values land as **proposals**, not live translations.                                                                                         | Approve in bulk afterwards, switch approval off for the migration, or migrate with a token that can approve. |
| `The glossary exceeds 10 MB in one request`                    | The glossary import is a single request, and this one is too big.                                                                                     | Split the glossary in Crowdin and migrate the halves.                                                        |

## When something fails

A migration is several write phases, and it is deliberately **not** a
transaction. If one fails, the command says what already landed:

```
error Migration failed during translation memory: Your organization is over its
plan limits and is read-only. (LIMIT_001)
  Already applied, and NOT rolled back: 8 language(s), 10 key(s), 10 description/tag update(s).
Re-run the same command: every step is idempotent, so what already landed is not duplicated.
```

Fix the cause, then run the same command again.

<Note>
  **Re-running is always safe**, and is both the recovery path and the ordinary
  one. Imports update rather than duplicate, a language that already exists is
  skipped, a TM or glossary already carrying the Crowdin one's name is reused, and
  descriptions and tags are only ever filled — so an edit you made in TextSetu in
  between survives the next run. There is no state file to clean up.
</Note>

Common causes:

| Message                                                                    | What to do                                                                          |
| -------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
| `No Crowdin token.`                                                        | Set `CROWDIN_TOKEN`.                                                                |
| `Crowdin rejected the token (401).`                                        | Expired or revoked — or an Enterprise token used without `--organization`.          |
| `Migrating a translation memory or glossary needs a personal access token` | You are using a project token. Run `textsetu login`, or pass `--only translations`. |
| `N TextSetu key(s) would be claimed by more than one Crowdin string`       | Two files share an identifier. Use `--on-collision prefix`, or rename in Crowdin.   |
| `Several Crowdin languages map onto the same TextSetu code`                | Separate them with `--locale-map`.                                                  |
| `TextSetu language codes are at most 10 characters`                        | Map the long tag to a shorter code with `--locale-map`.                             |
| `Your organization is over its plan limits and is read-only. (LIMIT_001)`  | Remove resources or upgrade, then re-run. Nothing already written is lost.          |
| `Crowdin … (403)` / `(404)`                                                | The token's owner cannot read that project, TM or glossary, or the id is wrong.     |

To undo rather than repeat: every phase is recorded as a job in the import
history, and each can be reverted from the UI. The migration never deletes
anything.

## What does not move

A migration is judged on what did not arrive, so each of these is a counted line
in `--dry-run` rather than a surprise afterwards.

* **Approval state.** Everything arrives as a value, or as a proposal on a project
  that requires approval. Crowdin's approved/unapproved distinction is not carried.
* **History, authors, timestamps and comments.** Only the current translation moves.
* **Deliberately-empty translations**, which arrive as untranslated.
* **File and directory structure.** Crowdin's files become key namespaces under
  `--on-collision prefix`, and are otherwise discarded. If your file tree is the
  thing you want to keep, `crowdin download` followed by
  [`textsetu init`](/api/cli#textsetu-init) and `textsetu push --translations`
  preserves it instead.
* **Screenshots.** TextSetu stores one screenshot per key with no regions, so
  Crowdin's many-to-many mapping with bounding boxes has nowhere to land.
* **`maxLength`**, folded into the key description.
* **Glossary synonyms** beyond the first per language.
* **A TM source text's leading and trailing whitespace.** The source is the TM's
  lookup key and is stored trimmed. Translations keep theirs, and so do all
  ordinary translation values.
* **Project settings** — QA checks, workflow steps, plural-rule overrides.
  Configure the TextSetu project first.

## Large projects

Translations upload as one multi-language document, split only when they would
exceed the 10 MB request limit. The translation memory is chunked at 500 segments
per request. Writes are paced under the API's rate limit, and Crowdin reads run
six at a time against its 20-concurrent cap. A project of a few thousand keys
takes a minute or two; if a run is interrupted, re-running it costs only the
reads.

## Next steps

<Note>
  **Moving your file sync too?** The [CLI guide](/api/cli#from-crowdin) maps
  `crowdin.yml` onto `textsetu.json` field by field, including the placeholder
  and `languages_mapping` equivalents.
</Note>

* [Import & Export](/docs/guides/import-export) — for anything the migration
  deliberately leaves out.
* [Translation Memory](/docs/guides/translation-memory) and
  [Glossary](/docs/guides/glossary) — what those assets do once they arrive.
* [Approval workflow](/docs/guides/approval-workflow) — if your values landed as
  proposals.
