Browse the help center

Localize a string catalog over the API

Maintained by Vitalii VlasiukCo-founder

UI text, game strings, and email templates live as keyed catalogs, and keys are what make localization automatable: Transept diffs your catalog by key, translates only what actually changed, and returns every language keyed the same way, so the loop costs one string when one string changes.

On this page

What is a string catalog?

Transept’s keyed format is .tstrings.json: a list of units, each with a stable id, a source, and optionally a context (what the string is, where it appears), a max_length, and placeholders. The id is the contract: it is how translations keep their identity across re-imports, how review state survives, and how the export keys every language. Both extras do real work: the context reaches the model as translator guidance, and a character limit is enforced with an automatic shorten-and-retry.

CSV, gettext PO, and XLIFF files import as the same keyed units through the app or the upload endpoint; see strings files for what each format maps to. This article is about driving the loop from code, where .tstrings.json is the native shape.

How do I import a catalog over the API?

POST /documents/import-strings with the catalog as JSON. Target languages resolve in a fixed order: an explicit target_language / target_languages in the request wins, else the catalog header’s target_locales, else the call fails immediately with no_target_languages instead of importing a document with no targets. The call returns a processing_job_id; poll GET /document-jobs/{id} until the import finishes and you have the document ids.

Translations the file already carries (targets: {de: "…"}) are seeded as each string’s active translation, free and with no run needed, and feed your translation memory immediately, the same behavior as bringing existing translations through the app.

# BASE="https://app.transept.ai/api/public/v1"; KEY from Settings → Developer
curl -s -X POST $BASE/documents/import-strings \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"document": {
        "format": "transept-strings", "version": 1, "source_locale": "en",
        "target_locales": ["de", "fr", "uk"],
        "metadata": {"name": "App UI"},
        "units": [
          {"id": "app.save", "source": "Save", "context": "Toolbar button"},
          {"id": "app.greeting", "source": "Welcome, {name}", "context": "Dashboard header"}
        ]}}'
# → { processing_job_id }; poll GET /document-jobs/{id}

How do I re-translate only what changed?

Re-submit the whole current catalog to POST /documents/{id}/import-strings, not a trimmed one. The server diffs it by unit id and answers with what it found (added, changed, unchanged, removed) plus delta_block_ids: the exact blocks that need work, per language version. Feed that object verbatim as the block_ids of a group run and only those blocks are translated; you can also pass run inline on the re-import to dispatch the delta in the same call.

Unchanged units keep their translations and any review already done on them; this is the API side of Run only what changed. Fixing one string in a catalog of hundreds costs one string.

# 1. Re-submit the whole current catalog; the server diffs it by unit id
curl -s -X POST $BASE/documents/<master>/import-strings \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d @app-ui.tstrings.json
# → { units: {added, changed, unchanged, removed}, delta_block_ids: {<doc_id>: [block ids]} }

# 2. Feed delta_block_ids VERBATIM into a group run; only those blocks translate
curl -s -X POST $BASE/group-runs \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"document_id": "<master>", "template_id": "end_to_end",
       "target_languages": "all", "block_ids": { "<doc_id>": ["<block id>", "…"] }}'

How do I get the results back keyed?

GET /documents/{id}/content?format=strings on the master document returns one .tstrings.json with every language’s translations keyed by unit id: one call, ready to commit or feed back into your build. The same endpoint serves per-language renderings (markdown, html, csv, xliff, tmx) when a pipeline wants a specific wire format instead; the full table is in Automate Transept with the API.

For catalogs that came in as CSV, PO, or XLIFF, the original-file export fills your own file’s cells and entries instead (byte-for-byte what you uploaded, translations added), so the file loads straight back into the system it came from.

curl -s "$BASE/documents/<master>/content?format=strings" \
  -H "Authorization: Bearer $KEY"
# → one .tstrings.json with every language keyed by unit id
Common questions

Do unchanged strings get re-billed?

No. On every re-import Transept diffs the submitted catalog against the stored one by unit id, and only new or changed units enter the run. Unchanged strings keep their translations and any review already done on them; re-submitting the whole file costs nothing by itself.

What happens to placeholders like {name}?

They are protected, not translated. Placeholders are masked before the text reaches the model, counted against the source after every reply, and a translation that lost or garbled one is repaired or retried; it never lands silently. The placeholder text itself is restored verbatim.

Do I need to work out the diff myself before re-importing?

No; that is the point of the loop. Always send the complete current catalog; the server owns the diff and answers with exactly which blocks changed. Sending a hand-trimmed file is actually wrong: units missing from the submission are treated as removed.

Still stuck? Ask Literess in the app, or write to [email protected].