Skip to main content

Translate and sync

Two routes put source strings into a project and queue their AI translation. POST /translate takes strings one by one, for an app that extracts them from code. POST /sync takes a whole translation file, for an app that keeps a catalog. Both answer at once with a cost estimate and a trackingToken, and the translation runs in the background.

All paths below are under https://api.easyi18n.com/v1/projects/{projectId}. The key needs the translate scope; see authentication.

Push strings​

Send up to 500 units per request, each a source of at most 5,000 characters. Price the push first with ?dryRun=true, which writes nothing:

curl -X POST "https://api.easyi18n.com/v1/projects/$PROJECT_ID/translate?dryRun=true" \
-H "Authorization: Bearer $EASYI18N_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"units": [
{"source": "Welcome back, {name}", "key": "home.welcome"},
{"source": "Sign out", "ctx": "account menu"}
]
}'

The preview reports estimatedCredits and the workspace balance, both in millicredits, and accepted: whether the real push would fit the balance and the plan's key limit. Send the same body without the query parameter to run it:

{
"trackingToken": "8Zk2pW5nYc",
"estimatedCredits": 1240,
"balance": 498760,
"baseCode": "en",
"targetLangs": ["es", "fr"],
"units": [
{
"key": "home.welcome",
"token": "3f9a…",
"outcome": "created",
"translated": [],
"pending": ["es", "fr"]
}
]
}

What a unit can carry:

  • source, or forms for a plural: an object keyed by CLDR category (one, other and so on) where other is required. A source can carry {name} placeholders.
  • ctx, a disambiguation context. Two identical sources with a different ctx are different units.
  • key, a preferred name for a new key: dot-separated segments of letters, digits and underscores, such as home.welcome. Any other shape is 400 invalid_key and the whole request is refused. Without it the key is named from the source.
  • previousSource (with previousCtx), when you edited the wording of a string. The unit keeps the existing key instead of creating a new one.

langs at the top level restricts the target languages. Without it the push covers every target language of the project. A language set to manual translation is never an AI target.

A push is idempotent. Sending a source the project already translated reports it with outcome: matched, queues nothing and returns trackingToken: null. Each unit's outcome is created, matched, carriedOver or contended. A contended unit lost a race with a concurrent write and nothing was written for it: push it again.

Two flags mean a unit was accepted but not translated, at no cost. gated means the project has automatic source review on and this source is not reviewed yet. disabled means the key is switched off in the dashboard, and pushing does not switch it back on. The response counts them in skippedGated and skippedDisabled; report those numbers, because the push otherwise looks complete.

Poll the translation​

Poll the job with the token. This read accepts the read or the translate scope:

curl "https://api.easyi18n.com/v1/projects/$PROJECT_ID/translations?trackingToken=8Zk2pW5nYc" \
-H "Authorization: Bearer $EASYI18N_TOKEN"
{
"trackingToken": "8Zk2pW5nYc",
"status": "done",
"estimatedCredits": 1240,
"creditsCharged": 1105,
"summary": {"total": 2, "translated": 2, "pending": 0, "failed": 0}
}

queued and running mean keep polling. done, failed, aborted_no_credits and cancelled are final. done says the job finished, not that every pair was translated: read summary.failed and summary.pending. After aborted_no_credits the job never resumes. Add credits and push the same strings again, which starts a new job with a new token.

To read one key, pass its name as ref, and optionally lang:

curl "https://api.easyi18n.com/v1/projects/$PROJECT_ID/translations?ref=home.welcome" \
-H "Authorization: Bearer $EASYI18N_TOKEN"
{
"key": "home.welcome",
"translations": {
"es": {"status": "translated", "value": "Bienvenido de nuevo, {name}"},
"fr": {"status": "pending"}
}
}

These reads return the live translations. Apps receive a translation once it is in a published version.

Recover a lost response​

If the connection drops after you send a push, you do not know whether it ran. Add a requestId of your own to the body, 1 to 128 characters of letters, digits, _ and - (a UUID fits). The API records it before it writes anything else:

  • Sending the same requestId with the same body within 24 hours returns the response of the first run, trackingToken included, and writes nothing.
  • The same requestId with a different body is 409 request_mismatch.
  • While the first request is still running, a repeat is 409 request_in_progress with leaseUntil, the time to check again.
  • After 24 hours a repeat is 409 request_expired. Do not resend it.

GET /requests/{requestId} reads the record without sending the body again. Its state is unknown (no such request under this key), processing, done (with the response), failed (with the status and body of the refusal) or expired (with stateWas and the trackingToken, if any). A request that failed is sent again under a new requestId.

POST /sync follows the same contract.

Sync a whole file​

POST /sync uploads one file, base64-encoded, and reconciles the project with it. It accepts two kinds of file:

  • An Apple String Catalog (xcstrings), which holds every language.
  • A base-language catalog in json, nested_json or angular_json: the one file per locale that Transloco, ngx-translate and i18next read, or the file ng extract-i18n --format=json writes.

Any other format is 400 format_not_syncable. The file's language has to be the project's base language, or the request is 400 source_language_mismatch. A file holds at most 1,000 entries.

curl -X POST "https://api.easyi18n.com/v1/projects/$PROJECT_ID/sync?dryRun=true" \
-H "Authorization: Bearer $EASYI18N_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"fileName\": \"en.json\", \"format\": \"json\", \"contentBase64\": \"$(base64 < public/i18n/en.json | tr -d '\n')\"}"

The answer counts what the sync did, or would do under dryRun: created, updatedSources, matchedSources, updatedTranslations, disabledOrphans and reenabled, plus estimatedCredits, balance and the trackingToken of the queued translation.

The file is the project's whole catalog. A key the file no longer carries is disabled: it stays in the project, out of published versions and out of AI translation, and it comes back when the file carries it again. Check disabledOrphans in the preview before the real run.

Translations the file already carries are stored as they are and never charged. A stored translation that a person edited or approved, or that the AI produced, is not overwritten in silence. By default it is kept and the pair is listed under conflicts with resolution: kept. Send "onConflict": "file" to let the file win. The list holds the first 50 pairs and conflictsTotal the real count.

To protect source text edited in the dashboard, send pulledAt: the snapshotAt that GET /translations returned with the files you are syncing back. A source the project changed after that instant is reported as a conflict in the base language and kept, unless onConflict is file.

Read diagnostics too. A LANG_NOT_IN_FILE row names a target language the file does not carry. On a dry run its plannedPairs says how many translations the run would queue for that language.

Capture untranslated strings at runtime​

POST /capture is for debug builds. A runtime SDK reports the strings it had to show untranslated, and the project registers each unknown one as a captured key for review in the dashboard. Captured keys are not translated and nothing is charged.

curl -X POST "https://api.easyi18n.com/v1/projects/$PROJECT_ID/capture" \
-H "Authorization: Bearer $EASYI18N_CAPTURE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"units": [{"source": "Sign out", "ctx": "account menu"}]}'
{"captured": 1, "skipped": 0}

A batch holds up to 200 units, and a project accepts 5,000 new captured keys per day. Use a key with the capture scope alone and keep it out of release builds.

Add a language or an output format​

Two routes with the write scope prepare a project without the dashboard. Neither spends credits.

curl -X POST "https://api.easyi18n.com/v1/projects/$PROJECT_ID/languages" \
-H "Authorization: Bearer $EASYI18N_TOKEN" \
-H "Content-Type: application/json" \
-d '{"code": "pt-BR"}'
{"code": "pt-br", "isBase": false, "fillMode": "ai"}

Adding a language that is already there changes nothing. Send "fillMode": "manual" to keep it out of AI translation.

POST /output-formats with {"format": "arb"} adds a file format the project publishes: json, nested_json, angular_json, arb, po, xcstrings, ios_strings or android_xml. A project holds at most two.

When a push or a sync is refused​

StatuserrorWhat to do
400too_many_units, source_too_long, too_many_entriesSplit the request.
400fill_mode_manuallangs names a language set to manual translation. Remove it.
402insufficient_creditsThe estimate is above the balance. availableCredits says what a new job may spend. Add credits and push again.
402spend_cap_exceededThe workspace's monthly spending cap refused it. capRemaining says what is left under the cap.
402plan_limitThe push would pass the plan's key limit. details names the limit.
403authorization_requiredThe key is an agent key and the work costs credits. Request an operation for estimatedCredits.
503unavailable, contendedRun the push or the sync again.