Translatorio Python SDK
The official Python client for Translatorio, the translation API.
pip install translatorio-sdk
import os
from translatorio import Client
tr = Client(os.environ["TRANSLATORIO_KEY"])
result = tr.translate("Add to cart", "es", glossary="shop", reference="release-42")
if result.intact:
publish(result.translation) # "Añadir a la cesta"
else:
hold_for_review(result.id) # a placeholder, tag or masked value did not come back
intact is the one field worth checking before publishing: a translation that lost
{name} or a <b> tag is not ready, however good the rest of it reads.
Translating text
One text or many, into one language or many. results keeps the order you sent.
result = tr.translate(
["Add to cart", "<b>Free</b> returns within {days} days"],
["es", "de", "ja"],
source="en", # left out, or "auto", it is detected
format="html", # text, html or markdown: decides what is protected
formality="informal", # default, formal or informal
context="buttons of a shop",
glossary="shop", # the slug of one of your glossaries
terms={"es": {"cart": "cesta"}}, # terms for this call only, per language or for all
anonymize=True, # the default: personal data is masked before the model sees it
reference="release-42", # your own id, handed back and kept in the history
)
result.translation # only for one text into one language, otherwise None
result.text("de", index=1) # one text in one language
result.translations # [{"es": "...", "de": "...", "ja": "..."}, {...}]
result.results # [{"index", "translations", "intact"}], as sent
result.intact # False when anything protected did not come back
result.source # "en", given or detected
result.detected # True when it was detected
result.charged # credits this call cost
result.remaining # credits left
result.segments # text by language pairs
result.cached # answered from your translation memory
result.corrected # answered with one of your corrections
result.anonymized # pieces of personal data masked
result.model_read # whether a model read it
result.model_why # "memory" or "test_key" when none did
result.effort # "low", "medium" or "high": the level it was served at
result.tokens # {"input": ..., "output": ...}
result.raw # the whole answer
An option the endpoint does not take is a TypeError here, before anything is sent: the
API refuses unknown fields with unknown_field, and an option left out is never sent.
Choosing how hard the model works
effort (default, low, medium or high) is accepted on translate(), translate_file(),
translate_image() and batch(), and sent only when you pass it. Leaving it out is the same as default: the level Translatorio recommends at the time (medium today), which moves as the models do. Name a level to keep it fixed.
tr.translate(text, "es", effort="high")
The answer says which level served it, in result.effort.
Translating a file
Send a whole translation file and get one file back per language, in the same shape:
keys, comments and structure untouched. JSON, PO, XLIFF, CSV, Android XML, iOS .strings,
Markdown, HTML and plain text.
from pathlib import Path
result = tr.translate_file(Path("lang/en.json"), ["es", "de"]) # read, and named after itself
for file in result.files:
Path("lang", file["name"]).write_text(file["content"]) # es.json, de.json
result.content("es") # one language's file
result.strings # how many strings the file held
result.format # "json"
result.corrected # strings answered with one of your corrections
result.effort # the level it was served at
# Bytes, with a name that says the format (or `format=`):
tr.translate_file(Path("Localizable.strings").read_bytes(), "fr", filename="Localizable.strings")
# Or the content as text:
tr.translate_file(catalogue_text, "fr", filename="messages.po")
A path or bytes are uploaded as multipart/form-data, exactly as they are, so a UTF-16
.strings file arrives byte for byte (it comes back translated as UTF-8). Text goes up as JSON. A plain str is always the
content, never a path: pass a pathlib.Path to read a file.
Translating an image
Every block of text in the picture, its translation into each language, and its box as fractions of the image, so you can lay the translation over the original.
result = tr.translate_image("https://cdn.example.com/sign.jpg", ["en", "es"])
result = tr.translate_image(Path("sign.jpg"), "en") # a file
result = tr.translate_image(open("sign.jpg", "rb").read(), "en") # bytes
result = tr.translate_image(base64_or_data_uri, "en") # already encoded
for block in result.blocks:
block["text"], block["box"], block["translations"]["en"]
An http or https address is fetched by the model provider; anything else is the picture itself and goes out as base64. PNG, JPEG, WebP or GIF.
Detecting a language
detection = tr.detect("Obrigado pela ajuda")
detection.code # "pt"
detection.language # {"code", "name", "native", "rtl"}, or None
detection.confidence # 0.97
detection.by # "script", "model" or "test_key"
Rarely needed: leaving source out of translate() detects it in the same call.
Batches
Many texts translated in the background. The call answers at once; poll the batch or let
the batch.completed webhook tell you.
batch = tr.batch(texts, ["es", "fr", "de"], glossary="shop", reference="catalogue-2026-10")
batch.id # "bat_..."
batch.credits_estimated # set aside now, so a burst of batches cannot spend the same credits
status = tr.batch_status(batch.id)
status.status, status.done, status.failed
translated = dict(status.translations) # index -> {language: text}
while status.next:
status = tr.batch_status(batch.id, cursor=status.next)
translated.update(status.translations)
status.failures # index -> {"code", "message"}: a bad item fails in its place
status.page # this page of results as a Page (items, next)
tr.batches(limit=10) # the recent ones, summarised
Glossaries
tr.create_glossary("Shop", {"cart": "cesta", "checkout": "caja"}, source="en", target="es")
tr.glossaries() # every glossary, with entry counts
glossary = tr.glossary("shop") # one, with its entries
glossary.terms # {"cart": "cesta", "checkout": "caja"}
# Only what you pass is sent. `entries` REPLACES the list; None unties a language.
tr.update_glossary("shop", entries=glossary.entries + [{"term": "basket", "translation": "cesta"}])
tr.update_glossary("shop", target=None)
tr.delete_glossary("shop")
A glossary with no target applies to every language, which is what brand names want.
Corrections
Your own translation of one exact text into one language. From then on that text is answered with yours, and the model is never asked again.
correction = tr.correct("Add to cart", "es", "Añadir a la cesta")
page = tr.corrections(target="es")
while page.next:
page = tr.corrections(target="es", cursor=page.next)
tr.delete_correction(correction.id)
The account
tr.languages() # every language: code, name, native name, right to left
tr.usage() # plan, credits, the last 30 days and prices. Works at zero credits.
tr.ping() # is the key good, is the service up
page = tr.translations(kind="text", reference="release-42") # the history, never the text
tr.translation_record(page.items[0].id)
tr.keys() # name, mode, prefix, last use. Never a secret.
tr.revoke_key(12) # including the one you are calling with. There is no create.
Paged lists
corrections() and translations() answer a Page: page.items (the rows, which you can
also iterate over) and page.next. Pass next back as cursor= for the following page;
None means the last one. next is opaque, so keep it as it came. A batch's results page
the same way: batch_status(id, cursor=...) and its next. The other lists
(languages(), keys(), glossaries(), batches()) are plain lists.
All of these are free.
Errors
Every refusal is a typed exception carrying the API's code, its message, the status
and the decoded payload.
from translatorio import (
AuthenticationError, InvalidRequest, NotFound, QuotaExhausted, RateLimited, ServerError,
)
try:
result = tr.translate(text, "es")
except QuotaExhausted as e:
# 402. Stop calling: no amount of retrying produces credits.
alert(f"needed {e.required}, {e.remaining} left, renews {e.renews_at}")
except InvalidRequest as e:
# 422: e.code is invalid_request, unknown_field, unsupported_language, too_long...
log(e.code, e.fields) # {field: [messages]}, empty when no one field is at fault
except RateLimited as e:
# 429, already retried. e.retry_after is what the service asked for, in seconds.
...
except ServerError as e:
# 5xx, or the service could not be reached (status 0). Already retried.
...
AuthenticationError is a 401, NotFound a 404, and anything else the API refuses is a
TranslatorioError, the base of them all.
Retries and idempotency
Retries the right failures and never the wrong one. A 429, a 5xx and an unreachable
service are asked again with a growing wait, twice by default (retries=). A 402 is never
retried, nor is any other 4xx: those answers do not change by asking again.
Waits as long as the service asked, and no longer. The wait a 429 or a call still in
flight carries (retry_after in the body, or the Retry-After header) beats guessing, and it is bounded by max_wait (30 seconds by default) all the same.
Makes those retries safe. Every call that changes something carries an
Idempotency-Key, generated once per call and reused by its retries (a file upload is
resent byte for byte), so a request that
timed out and is asked again is translated once and billed once. Pass your own with
idempotency_key= on the translation calls to make a retry from your side the same call.
Never reads a non-answer as an answer. Only a 2xx whose body is a JSON object is one.
A redirect (never followed, so your key goes nowhere it was not sent), an empty body or a
proxy's HTML page is a retryable ServerError.
timeout= (120 seconds by default) applies to connecting and to every read, so a stalled
body fails like a stalled connection.
Webhooks
Your endpoint URL is public. Verify before you act:
from translatorio import webhooks
event = webhooks.event(
request.get_data(), # the RAW body
request.headers[webhooks.HEADER], # X-Translatorio-Signature
os.environ["TRANSLATORIO_WEBHOOK_SECRET"],
)
if event is None:
return "", 400
if event["event"] == webhooks.BATCH_COMPLETED:
fetch_results(event["data"]["id"])
Events: batch.completed, credits.low and quota.exhausted. A delivery older than five
minutes is refused (tolerance= to change it), so a captured one cannot be replayed.
Testing with a transport of your own
Python 3.9+. No dependencies: the standard library does the HTTP. Pass anything with a
send() as transport= to put requests through your own stack, or to test without a
network:
class Stub:
def send(self, method, url, headers, body):
return 200, '{"translation": "Hola", "intact": true}'
tr = Client("tr_test_...", transport=Stub())
send() returns (status, body) or (status, body, headers), and reports a request that
got no answer by raising ServerError, which is what gets retried.
The client's own suite runs over a stub transport: python3 -m unittest discover -s tests.
Author
Created by Edu Lazaro for Translatorio, the translation API this client speaks to.
License
The Translatorio Python SDK is open-sourced software licensed under the MIT license.
Metadata
Release files for translatorio-sdk 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| translatorio_sdk-1.0.0.tar.gz | 42.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| translatorio_sdk-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 69.4 kB
Release files / translatorio_sdk-1.0.0.tar.gz
| Download URL | translatorio_sdk-1.0.0.tar.gz |
|---|---|
| Size | 42.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
c0bd9710184cde8de399efec519a33f704036abcea14075b770e88e9ba8b92ab
|
|
BLAKE2b-256 checksum How to use checksums |
5ff4ac43926e0092e2afa572ac0324d2f1beffd58041563b2bace7430839455d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.
Transparency logRelease files / translatorio_sdk-1.0.0-py3-none-any.whl
| Download URL | translatorio_sdk-1.0.0-py3-none-any.whl |
|---|---|
| Size | 26.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ac9678468c1a4e0276df40caa81a1f57144a5df970fbf153ddde626c708cebac
|
|
BLAKE2b-256 checksum How to use checksums |
3afa5c2570230931526f47e208b3812644de7cfa0789ad0f0d657ab4df4696dd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.
Transparency log