DEV Community

Louis Desclous
Louis Desclous

Posted on Fully Autonomous

Localizing product images for every marketplace with a Python script

Translating a product listing is mostly solved: titles, bullets and descriptions go through any translation API. The images are where localization usually stops. Infographics with dimensions and feature callouts, "what's in the box" visuals, size charts and supplier photos with Chinese labels all keep their original text, and on a marketplace the images are what shoppers read first.

This tutorial builds a script that takes a folder of product images and a list of target languages, sends each image to an image translation API, and saves the translated versions next to the originals, ready to review.

It uses TranslateMyImage, which I build. The API detects the text in the image, translates it, and returns a new image with the translation placed where the original text was.

What you need

  • API access, which is included from the Pro plan. Each completed translation consumes one credit.
  • An API key from the API Keys page of the dashboard. It is shown once, so store it right away.
  • Python 3.9+ and requests.
pip install requests
export TMI_API_KEY="your-key"
Enter fullscreen mode Exit fullscreen mode

The endpoint takes multipart/form-data (the full reference is at translatemyimage.com/api-docs):

Field Required Values
file yes JPG, PNG or WEBP, up to 10 MB and 25 megapixels
target_lang yes en, fr, es, de, it, pt, ja, ko, zh
source_lang no a language code, or auto (the default)
quality no basic (default) or ultra
business_type no default, marketing, edition, technical documentation

One image

import os
from pathlib import Path

import requests

API_URL = "https://translatemyimage.com/api/translate"
HEADERS = {"X-API-Key": os.environ["TMI_API_KEY"]}


class TranslationError(Exception):
    def __init__(self, status: int, payload: dict):
        super().__init__(f"{status}: {payload.get('error')} ({payload.get('code')})")
        self.status = status
        self.code = payload.get("code")


def translate_image(path: Path, target_lang: str) -> dict:
    with path.open("rb") as f:
        r = requests.post(
            API_URL,
            headers=HEADERS,
            files={"file": (path.name, f)},
            data={
                "target_lang": target_lang,
                "source_lang": "auto",
                "business_type": "marketing",
            },
            timeout=300,
        )
    payload = r.json()
    if not r.ok or not payload.get("success"):
        raise TranslationError(r.status_code, payload)
    return payload["translation"]
Enter fullscreen mode Exit fullscreen mode

A successful response looks like this:

{
  "success": true,
  "translation": {
    "id": "clxb1234567890",
    "originalUrl": "https://…/original.jpg",
    "translatedUrl": "https://…/translated.jpg",
    "sourceLang": "zh",
    "targetLang": "en",
    "status": "completed"
  }
}
Enter fullscreen mode Exit fullscreen mode

business_type: "marketing" suits ad creatives and listing images. Use "technical documentation" for manuals, spec sheets and diagrams.

A folder, several languages

import time

LANGS = ["de", "fr", "es", "it"]  # e.g. the EU Amazon marketplaces


def translate_with_retry(path: Path, lang: str, attempts: int = 4) -> dict:
    for attempt in range(attempts):
        try:
            return translate_image(path, lang)
        except TranslationError as error:
            transient = error.status == 429 or error.code == "service_unavailable"
            if not transient or attempt == attempts - 1:
                raise
            time.sleep(2 ** attempt * 5)


def localize_folder(src: str = "images", out: str = "localized") -> None:
    for path in sorted(Path(src).glob("*")):
        if path.suffix.lower() not in {".jpg", ".jpeg", ".png", ".webp"}:
            continue
        for lang in LANGS:
            target = Path(out) / lang / path.name
            if target.exists():
                continue  # already done: re-running the script costs nothing
            result = translate_with_retry(path, lang)
            target.parent.mkdir(parents=True, exist_ok=True)
            target.write_bytes(requests.get(result["translatedUrl"], timeout=60).content)
            print(f"{path.name} → {lang}: {target}")


if __name__ == "__main__":
    localize_folder()
Enter fullscreen mode Exit fullscreen mode

You end up with localized/de/…, localized/fr/… and so on, mirroring the source folder. Ten images into four languages is 40 credits.

Errors worth handling

Error responses are JSON with success: false and a message in error. Once the request is authenticated they also carry a stable code, so you can branch without parsing English:

  • plan_required: the key's account does not include API access (Pro and above) or the option you asked for.
  • insufficient_credits: top up or wait for the monthly reset. Stop the loop instead of retrying.
  • invalid_image / invalid_request: wrong format, over 10 MB or 25 megapixels, or a bad language code.
  • content_blocked: the image was refused by the safety check. Skip it.
  • 429 or service_unavailable: transient, retry with a backoff as above.

Review before you publish

Treat the output as a strong first draft. Check numbers, units and brand names on every image, and have a native speaker glance at anything legal or safety related. The script's folder layout makes that review easy: open localized/de and images side by side.

If you only need a handful of images, the same thing works without code at translatemyimage.com: drop an image, pick a language, download the result.

What kind of images do you need localized? Product photos, comics, UI screenshots? I would like to hear which cases break.

Top comments (0)