Skip to content

Migrating an existing project

View .md

Four things go wrong in a migration: partial imports, encoding damage, lost history and comments, and drift between the old source and the new one during the cutover. Sequence the work so all four stay visible.

Comvi caps translation keys per organization and enforces the cap on every key created. The import validator warns before the commit with the estimated new key count, current usage and the limit. Count your source keys first; if the number is above the plan’s limit, cut scope or upgrade before you import. Going over the key, member or project limit puts the organization into read-only, where every write fails with 403 READ_ONLY_MODE, so leave headroom. Keys, projects, members and MT credits are all capped per plan, and languages are not; read the current limits.

Metric Why it matters
Key count Checked against the key limit before anything else
Keys per namespace or file Drives the namespace layout
File formats Comvi imports JSON files and ZIP archives of JSON only. i18next-shaped JSON imports, and non-ICU plural syntax is converted; anything else needs converting first
Placeholder format {{name}} and %s are converted or need converting to ICU
Scripts in use Arabic, Hebrew and CJK content is where encoding damage shows

Decide the cutover model too. A hard cutover freezes the old system and Comvi takes over; it needs a freeze window. Writing to both systems for a while is lower risk and costs a reconciliation pass at the end. A dataset that fits one import batch usually takes a hard cutover of a day or two.

Before touching the full dataset, take the smallest namespace or the top 200 keys, one source language plus one target, through the whole import. Check keys, values, statuses, ICU placeholders, special characters and line breaks against the source. Budget a day.

  1. Create the project with your source language (Projects). It cannot be changed after creation.

  2. Add the target languages (Languages). Codes are canonical BCP-47: pt-BR, zh-Hans, en-US.

  3. Create the namespaces matching the current file layout. Do not restructure while migrating; a namespace with keys cannot be deleted.

  4. Import the glossary, so the glossary check has something to say from the first review. See Glossaries.

  5. Import the translation memory as TMX if the old system exports it, so fuzzy matches are live afterwards. See Translation memory.

  1. Export from the old system into one JSON file per language per namespace, and normalize: {{name}} to {name}, %s to {arg1}, stray braces escaped as '{', UTF-8 only (file -I *.json).

  2. Lay the archive out so detection works. The wizard reads the namespace and locale from each path:

    import.zip/
    ├── common/
    │ ├── en.json
    │ ├── de.json
    │ └── …
    └── checkout/
    ├── en.json
    └── …

    The mapping table on the import screen lets you set namespace and locale per file and create a missing namespace before committing. Limits per batch: 50 files, 10 MB each, 500 entries per ZIP, 100,000 keys, and a session expires after one hour.

  3. Read the validation report. Uploading validates without writing: per file, the detected format, conflicts with existing content, and whether it parsed. The summary counts files, total keys and unique keys, and flags a key-limit overrun.

  4. Resolve conflicts. A conflict is a key and locale that already holds a different value. Choose Keep existing or Use imported per conflict or in bulk, and set the Unresolved Conflict Policy for the rest. A first pass into an empty project has nothing to conflict with.

  5. Commit, then check what landed. Every non-empty value lands as Translated with the importing user as reviewer; empty values land as Not Translated. To keep a review step for untrusted content, select the imported rows on the Translations page and use Status → Mark as Needs Review.

  6. Import the remaining languages three to five at a time, so each job stays short and an error stays attributable.

See Import & export for the full wizard reference.

GET /v1/projects/:id/locales/stats returns per-locale totals and percentages, and the Languages page shows the same numbers.

  • Total keys should equal the inventory. A shortfall means a file failed to parse or was left unmapped.
  • Translated count per locale should match what the old system called approved.
  • Placeholder integrity. For every source {foo}, each target should contain {foo}. Comvi does not verify this on save or import.
  • Encoding. Search for ? runs and sequences like é and ’.

Fix the gaps with a second, targeted import.

  1. Point staging at the new endpoint. Enable Settings → Content Deploy, copy the endpoint URL, and pass it to the fetch loader as cdnUrl in staging.

  2. Run the app’s QA suite against staging. Watch plurals, right-to-left languages, and long-string languages such as German and Russian.

  3. Swap production. Change the production cdnUrl, deploy, and keep the old system readable but not writable as fallback.

  4. Watch for missing keys for a day. A key that did not arrive shows up as a missing-key warning from the i18n library at runtime.

  5. Decommission. Revoke the old system’s credentials, archive the old export files, and send the translation team the editor URL and Your first project.

Comvi imports values, not the old system’s change history or comments, and neither can be back-dated. Either accept the break, with Comvi’s history starting at the import, or export the old history as CSV and keep it next to the repository. Move any comment a translator needs into the key’s Internal notes (not sent to MT), or into AI translation context (sent to MT) if a provider should see it too.

Phase Duration
Inventory and pilot 1 to 2 days
Normalization and validation 1 to 2 days
Full import and reconciliation 1 day
Staging QA 1 to 2 days
Cutover and monitoring 1 day

For roughly 3,000 keys across 10 languages, five to eight days in total. The longest parts are outside Comvi: converting placeholder syntax and running your own QA suite. Freeze writes on both systems for the cutover window, and check whether the old system’s “translated” means per-language approval before deciding what the import should write.