Cross-border sellers often list the same product on Shopee, Lazada, Amazon, TikTok Shop and their own store, in several languages. A language model can draft titles, bullet points and descriptions for hundreds of products overnight from a spreadsheet of product facts. This guide builds a small Python script that reads a CSV file, asks a local model in Ollama (or any OpenAI-compatible AI API) for a listing in each language, and writes the results to a new CSV file that you review and then import into your marketplace templates.
Create a spreadsheet with one row per product and save it as products.csv in UTF-8 (in Excel: CSV UTF-8). Column names are up to you; every column is passed to the model, and the script needs a unique sku column:
sku,name,category,specs,keywords
LS-01,Bamboo laptop stand,Office,"6 height levels; fits 10-17 inch laptops; foldable; 1.1 kg; natural bamboo","laptop stand, bamboo, ergonomic"
BT-07,Wireless earbuds,Audio,"Bluetooth 5.3; 30 h playback with case; IPX5; USB-C charging","wireless earbuds, bluetooth, sport"
The more precise the facts (material, size, compatible models, package contents), the better and safer the result, because the script tells the model to use only these facts.
apt install -y python3-venv
python3 -m venv /opt/listing
/opt/listing/bin/pip install openai
The openai Python library works with Ollama's OpenAI-compatible endpoint and with most AI providers, so the same script runs against a local or a cloud model. Ollama setup is covered in running a local LLM with Ollama.
cat > /opt/listing/listing.py <<'EOF2'
import csv, json, os, re, sys, time
from openai import OpenAI
client = OpenAI(base_url=os.environ.get("AI_BASE_URL", "http://127.0.0.1:11434/v1"),
api_key=os.environ.get("AI_API_KEY", "ollama"))
MODEL = os.environ.get("AI_MODEL", "qwen2.5:7b")
LANGS = [l.strip() for l in os.environ.get("LANGS", "English").split(",")]
SYSTEM = """You write e-commerce product listings for online marketplaces.
Use only the product facts provided. Never invent features, materials,
certifications, prices or delivery promises. No exaggerated claims.
Return JSON only, in this format:
{"title": "...", "bullets": ["...", "...", "...", "...", "..."], "description": "..."}
title: at most 120 characters, main keyword first.
bullets: 5 short points, each starting with the benefit.
description: 80 to 150 words of plain text."""
def ask(product, lang):
prompt = f"Write the listing in {lang}.\nProduct facts:\n{json.dumps(product, ensure_ascii=False)}"
for attempt in range(3):
r = client.chat.completions.create(model=MODEL, temperature=0.4, messages=[
{"role": "system", "content": SYSTEM}, {"role": "user", "content": prompt}])
m = re.search(r"\{.*\}", r.choices[0].message.content or "", re.S)
try:
data = json.loads(m.group(0))
if data.get("title") and isinstance(data.get("bullets"), list) and data.get("description"):
return data
except (AttributeError, ValueError):
pass
time.sleep(2)
raise RuntimeError(f"no valid answer for {product['sku']} / {lang}")
src, dst = sys.argv[1], sys.argv[2]
done = set()
if os.path.exists(dst):
with open(dst, encoding="utf-8-sig") as f:
done = {(r["sku"], r["language"]) for r in csv.DictReader(f)}
with open(src, encoding="utf-8-sig") as f:
products = list(csv.DictReader(f))
new_file = not os.path.exists(dst)
with open(dst, "a", newline="", encoding="utf-8-sig") as f: # utf-8-sig: Excel shows every language correctly
w = csv.writer(f)
if new_file:
w.writerow(["sku", "language", "title", "bullets", "description"])
for p in products:
for lang in LANGS:
if (p["sku"], lang) in done:
continue
d = ask(p, lang)
w.writerow([p["sku"], lang, str(d["title"]).strip(),
"\n".join(str(b).strip() for b in d["bullets"]), str(d["description"]).strip()])
f.flush()
print(p["sku"], lang, "done", flush=True)
EOF2
The script asks for JSON, checks the answer and retries up to three times. Finished rows are written immediately, and a second run skips products and languages that are already in the output file, so an interrupted job simply continues where it stopped.
cd /data/listings
LANGS="English,Thai,Malay,Japanese" nohup /opt/listing/bin/python /opt/listing/listing.py \
products.csv listings.csv > listing.log 2>&1 &
tail -f listing.log
On a CPU server a 7B model typically needs tens of seconds per listing, so 500 products in four languages is an overnight job. To use a cloud model instead, set the API address, key and model name of any OpenAI-compatible API:
AI_BASE_URL=https://ai.example.com/v1 AI_API_KEY=sk-your-key AI_MODEL=cloud-chat \
LANGS="English,Thai" /opt/listing/bin/python /opt/listing/listing.py products.csv listings.csv
Use AI providers only from regions they support and within their terms.
listings.csv in Excel or Google Sheets and filter by language.SYSTEM, for example a list of words you never use, British or American English, or no emoji.SYSTEM as an example; models copy its structure and tone closely.qwen2.5:14b write noticeably better Thai, Malay and Vietnamese, at the cost of speed and memory.Marketplaces care about accuracy and policy compliance, and search engines about whether content is helpful. Accurate, specific listings that a person has reviewed are fine; mass-produced text with errors or invented claims is a risk whoever wrote it.
The model returned something that was not valid JSON three times in a row. Run the same command again (it resumes), lower the temperature, or use a larger model.
Yes. To cut out products and create white-background main images in bulk, see removing product photo backgrounds in bulk with rembg.
Still stuck after following these steps? Open a support ticket and the IMIDC 24/7 technical team will help. Include the server IP, the model you use, the command you ran and the last lines of listing.log so we can pinpoint the issue faster.