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, orformsfor a plural: an object keyed by CLDR category (one,otherand so on) whereotheris required. A source can carry{name}placeholders.ctx, a disambiguation context. Two identical sources with a differentctxare different units.key, a preferred name for a new key: dot-separated segments of letters, digits and underscores, such ashome.welcome. Any other shape is400 invalid_keyand the whole request is refused. Without it the key is named from the source.previousSource(withpreviousCtx), 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
requestIdwith the same body within 24 hours returns the response of the first run,trackingTokenincluded, and writes nothing. - The same
requestIdwith a different body is409 request_mismatch. - While the first request is still running, a repeat is
409 request_in_progresswithleaseUntil, 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_jsonorangular_json: the one file per locale that Transloco, ngx-translate and i18next read, or the fileng extract-i18n --format=jsonwrites.
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
| Status | error | What to do |
|---|---|---|
| 400 | too_many_units, source_too_long, too_many_entries | Split the request. |
| 400 | fill_mode_manual | langs names a language set to manual translation. Remove it. |
| 402 | insufficient_credits | The estimate is above the balance. availableCredits says what a new job may spend. Add credits and push again. |
| 402 | spend_cap_exceeded | The workspace's monthly spending cap refused it. capRemaining says what is left under the cap. |
| 402 | plan_limit | The push would pass the plan's key limit. details names the limit. |
| 403 | authorization_required | The key is an agent key and the work costs credits. Request an operation for estimatedCredits. |
| 503 | unavailable, contended | Run the push or the sync again. |