Turn any brand's product data into platform-ready, multilingual commerce assets. 把任何品牌的商品数据,一键变成多平台、多语言的电商素材。
Selling cross-border, the bottleneck usually isn't the product — it's content.
Every SKU has to be re-published for every destination, and each destination wants a different shape of the same facts:
- A spec card for the listing gallery, a 1:1 square for social, a tall detail page for A+ / 详情页.
- Amazon wants a title under 200 characters and at most 5 bullets. Google Merchant wants a TSV feed with
id/title/price/brand. Shopify wants an import CSV with its own column names. - And all of it again in the next language.
Done by hand, that is (SKUs × formats × platforms × languages) copies of the same information, each drifting out of sync with the others. Teams end up maintaining per-category templates, per-platform spreadsheets, and per-language documents that nobody can keep aligned.
Letusgoshopping makes it data-driven. You describe a product once, in one neutral JSON schema. The engine generates the images-ready HTML, the platform feeds, and the translated fields from that single source. Change the data, regenerate everything.
一句话:商品数据写一次,多平台 / 多格式 / 多语言的素材自动生成——不用为每个类目改代码。
Who it's for: cross-border sellers, brand/agency content teams, and anyone maintaining product listings across more than one platform or language. What you need: Python 3.9+ and your product data (or one of the bundled examples to try it on).
The engine is brand- and category-agnostic: category differences live in the data
(attribute_groups, dynamic-column spec_table), never in code — adding a category means
adding data, not adding code.
| Capability | What you get |
|---|---|
| One product schema | Describe a product once in JSON — name, brand, price, marketing bullets, attribute groups, and a spec table whose columns you define per product. Every language variant lives in the same file. |
| Three asset formats | Render the same product as a dense spec card, a 1:1 social square, or a tall detail page — three different templates, zero data changes. |
| Three platform adapters | Convert the same product into Amazon listing fields (with Seller Central limits applied), a Google Merchant Center TSV feed, or a Shopify product-import CSV. (Shopify is on main; it is not in the v0.1.0 tag — see CHANGELOG.md [Unreleased].) |
| Multilingual output | Any text field can hold multiple languages. Missing translations get filled by an offline glossary, a human-edited translation-memory file, or the Claude API — layered so a curated translation always wins. |
| One-command full kit | kit runs the whole pipeline — localize, render every template, run every platform adapter, write a manifest — into a single output folder. |
| Validation | build checks every product against the JSON Schema before rendering and reports problems per file instead of failing the whole run. (adapt and kit skip validation — run build first if you want the report.) |
| Data tooling | Scripts to ingest a vendor's raw parameter files into the schema, and to de-identify a real dataset into a redistributable example. |
flowchart LR
SRC["Raw vendor data"] -->|"scripts/ingest_sa.py"| DATA["products/*.json<br/>one neutral schema"]
DATA --> B["build<br/>validate, then render"]
DATA --> L["localize<br/>fill missing languages"]
DATA --> A["adapt"]
DATA --> K["kit<br/>localize in memory, then<br/>every template + platform"]
B --> T1["sku-card"]
B --> T2["social-square"]
B --> T3["detail-page"]
A --> P1["Amazon JSON"]
A --> P2["Google Merchant TSV"]
A --> P3["Shopify CSV"]
Only build validates against schema/product.schema.json; adapt and kit assume the
data is already clean.
The core is small on purpose: listingforge/ loads and validates, templates/ renders,
adapters.py maps to platforms. Each is an independent extension point — most
contributions are one new file. See CONTRIBUTING.md.
| Requirement | Notes |
|---|---|
| Python 3.9 or newer | Tested in CI on 3.9, 3.11 and 3.12. |
| Git | Templates, the schema, and the examples live at the repo root, so the tool is designed to run from a clone (or an editable install of one). |
| A browser | Output is HTML — you open it to look at it. Optional. |
git clone https://github.com/KeepIntouch2024/Letusgoshopping.git
cd Letusgoshopping
# Option A — just the runtime dependencies
pip install -r requirements.txt
# Option B — editable install (also gives you the `listingforge` command)
pip install -e ".[dev]"That installs two packages: jinja2 (required — the templating engine) and
jsonschema (optional — enables full JSON-Schema validation; without it the tool falls
back to minimal structural checks and still runs).
Verify the install:
python -m unittest discover -s tests -v # 9 tests, stdlib only — no pytest needed
python -m listingforge build examples/apparel-demo --lang enNone are required. Everything above works fully offline.
One variable matters only if you want LLM translation (--provider anthropic, or
--provider auto when you want it to pick Claude automatically):
| Variable | Required? | Purpose |
|---|---|---|
ANTHROPIC_API_KEY |
Optional | Credentials for the Claude translation provider. |
ANTHROPIC_AUTH_TOKEN |
Optional | Alternative to the above; either one is accepted. |
export ANTHROPIC_API_KEY="sk-ant-..." # only for `--provider anthropic`
pip install anthropic # the LLM provider is an optional dependencyIf neither variable is set, --provider auto silently falls back to the offline glossary —
so CI and first-time users never need a key.
说明:不配任何环境变量也能完整跑通;
ANTHROPIC_API_KEY只在用 Claude 做翻译时才需要。
Optional extras
pip install -e ".[llm]" # anthropic — enables `localize --provider anthropic`
pip install -e ".[schema]" # jsonschema — full JSON-Schema validation
pip install -e ".[render]" # playwright — then: playwright install chromium[render] is for scripts/render_png.py, which rasterizes built HTML cards to PNG.
From the repo root, after installing:
# 1. Render a spec card for every product in the demo, in English
python -m listingforge build examples/apparel-demo --lang en
# 2. Open the result in your browser
# macOS: open · Linux: xdg-open · Windows: start
open examples/apparel-demo/output/sku-card/index.htmlThat's it. You just turned examples/apparel-demo/products/*.json into browsable HTML
cards. Now try the whole pipeline in one command:
python -m listingforge kit examples/apparel-demo --lang zhwhich localizes, renders all three templates, runs all three platform adapters, and writes
a manifest — into examples/apparel-demo/kit/zh/.
The point of the schema is that nothing above is apparel-specific. The bundled
examples/fly-demo is an anonymized fly-fishing dataset (188 products, dense technical spec
tables) and it runs through the identical commands and the identical templates:
python -m listingforge build examples/fly-demo --lang zh
python -m listingforge kit examples/fly-demo --lang zh两个完全不同的类目(服装 / 飞钓线),同一套命令、同一套模板——这就是本项目的核心。
python -m listingforge <command> <example-dir> [options]<example-dir> is any directory containing a products/ folder of *.json files (the tool
also accepts a directory of *.json files directly).
| Command | What it does |
|---|---|
build |
Renders one template into HTML, one file per product, plus an index.html. |
localize |
Fills in missing-language fields using a translator, writing updated JSON. |
adapt |
Converts the products into one platform's format. |
kit |
Everything at once: localize in memory → all templates → all platforms → manifest. |
Common invocations:
# A different asset format from the same data
python -m listingforge build examples/apparel-demo --lang en --template social-square
python -m listingforge build examples/apparel-demo --lang en --template detail-page
# Platform feeds
python -m listingforge adapt examples/apparel-demo --platform amazon --lang en
python -m listingforge adapt examples/apparel-demo --platform google-merchant --lang en
python -m listingforge adapt examples/apparel-demo --platform shopify --lang en
# Fill missing Chinese fields using the offline glossary, writing to a new folder
python -m listingforge localize examples/fly-demo --lang zh --provider glossary --out /tmp/localized
# Use a human-curated translation memory (layered on top — the TM always wins)
python -m listingforge localize examples/fly-demo --lang zh --tm my-translations.json
# Use Claude for real translation (needs ANTHROPIC_API_KEY)
python -m listingforge localize examples/fly-demo --lang zh --provider anthropicFull option reference
Every command takes the example directory as its first positional argument.
build — render SKU cards from an example's product data
| Option | Default | Notes |
|---|---|---|
--lang |
en |
Language code, e.g. en / zh. |
--template |
sku-card |
One of sku-card, social-square, detail-page, or any folder you add under templates/. |
--out |
<example>/output/<template> |
Output directory. |
localize — fill missing-language i18n fields via a translator
| Option | Default | Notes |
|---|---|---|
--lang |
zh |
Target language code. |
--provider |
auto |
auto | anthropic | glossary | memory. auto uses Claude when credentials are present, else the offline glossary. |
--model |
claude-opus-4-8 |
Claude model, used only with --provider anthropic. |
--tm |
— | Translation-memory JSON file ({"english": "target"}), layered on top of the chosen provider. Required for --provider memory. |
--out |
overwrite in place | Output directory; omit to edit the source files. |
adapt — convert products to a platform's format
| Option | Default | Notes |
|---|---|---|
--platform |
required | amazon | google-merchant | shopify. |
--lang |
en |
Language code. |
--out |
<example>/adapters/<platform> |
Output directory. |
kit — one command: localize + all templates + all platform adapters
| Option | Default | Notes |
|---|---|---|
--lang |
zh |
Target language code. |
--provider |
auto |
Same choices as localize. Localization happens in memory — source files are untouched. |
--model |
claude-opus-4-8 |
Claude model for --provider anthropic. |
--tm |
— | Translation-memory JSON. |
--templates |
all three | Comma-separated list, e.g. sku-card,detail-page. |
--out |
<example>/kit/<lang> |
Output directory. |
If you installed with pip install -e ., a listingforge console command is available and
is equivalent to python -m listingforge.
Products live in <example>/products/*.json. Only id and identity.name are required —
everything else is optional. Here is the real
examples/apparel-demo/products/ACME-TEE-42.json, abridged:
{
"id": "ACME-TEE-42",
"identity": {
"name": { "en": "Organic Cotton Crew Tee", "zh": "有机棉圆领T恤" },
"brand": "ACME",
"series": "Everyday Basics"
},
"taxonomy": { "category": "Apparel", "tags": ["unisex", "cotton", "tee"] },
"pricing": { "currency": "USD", "value": 29.00 },
"copy": {
"bullets": {
"en": [
"180gsm 100% organic ring-spun cotton, breathable and soft",
"Pre-shrunk for a lasting fit wash after wash",
"Reinforced shoulder seams and ribbed crew neck",
"Unisex regular fit"
],
"zh": [
"180gsm 100% 有机环纺棉,透气柔软",
"预缩处理,多次水洗不变形",
"加固肩缝 + 罗纹圆领",
"中性常规版型"
]
}
},
"attribute_groups": [
{ "group": { "en": "Material", "zh": "材质" }, "values": [
{ "label": { "en": "100% Organic Cotton", "zh": "100% 有机棉" }, "icon": "cotton" },
{ "label": { "en": "180 gsm", "zh": "180 克重" } }
]},
{ "group": { "en": "Care", "zh": "洗护" }, "values": [
{ "label": { "en": "Machine Wash Cold", "zh": "冷水机洗" }, "icon": "wash-cold" }
]}
],
"spec_table": {
"columns": [
{ "key": "size", "label": { "en": "SIZE", "zh": "尺码" } },
{ "key": "chest", "label": { "en": "CHEST", "zh": "胸围" } },
{ "key": "length", "label": { "en": "LENGTH", "zh": "衣长" } }
],
"rows": [
{ "size": "S", "chest": "36\" / 91cm", "length": "27\" / 69cm" },
{ "size": "M", "chest": "38\" / 96cm", "length": "28\" / 71cm" },
{ "size": "L", "chest": "41\" / 104cm", "length": "29\" / 74cm" }
]
},
"localization": { "units": "dual", "size_system": "US->EU/UK" }
}(Abridged — the real file also has a third Fit attribute group, two more Care values, an
eu spec column and an XL row; all of them show up in the Amazon search_terms below.)
Two things carry the whole design:
attribute_groups— the group names and their values are data. Apparel getsMaterial/Care/Fit; the fishing example getsWater Type/Species/Material. Same template, no code change.spec_table.columns— you declare the columns per product. Apparel usessize / chest / length / eu; a tippet spool usesitem / size / diameter / break_strength.
Every user-visible string is a {"lang": "text"} map, which is what makes multilingual
output a data property rather than a separate pipeline.
$ python -m listingforge build examples/apparel-demo --lang en
✓ ACME-HOODIE-07.json → examples/apparel-demo/output/sku-card/ACME-HOODIE-07.en.html
✓ ACME-TEE-42.json → examples/apparel-demo/output/sku-card/ACME-TEE-42.en.html
2/2 rendered → examples/apparel-demo/output/sku-card/ (open examples/apparel-demo/output/sku-card/index.html)examples/apparel-demo/output/
└── sku-card/ # one folder per template
├── index.html # linked list of everything rendered
├── ACME-HOODIE-07.en.html
└── ACME-TEE-42.en.html # <id>.<lang>.html
Each HTML file is fully self-contained (inline CSS) — open it in a browser, or screenshot it for the listing gallery.
$ python -m listingforge adapt examples/apparel-demo --platform amazon --lang en
2 Amazon listing(s) → examples/apparel-demo/adapters/amazon/ (0 constraint issue(s))adapters/amazon/ACME-TEE-42.json — Amazon Seller Central fields. Note the bullets were
taken from copy.bullets, the search terms were synthesized from attribute_groups, and
the description was assembled because the source had none:
{
"title": "ACME Organic Cotton Crew Tee",
"brand": "ACME",
"bullet_points": [
"180gsm 100% organic ring-spun cotton, breathable and soft",
"Pre-shrunk for a lasting fit wash after wash",
"Reinforced shoulder seams and ribbed crew neck",
"Unisex regular fit"
],
"description": "ACME Organic Cotton Crew Tee. 180gsm 100% organic ring-spun cotton, breathable and soft. Pre-shrunk for a lasting fit wash after wash. ...",
"search_terms": [
"100% Organic Cotton", "180 gsm", "Machine Wash Cold",
"Tumble Dry Low", "Do Not Bleach", "Regular / Unisex"
],
"standard_price": "29.00 USD"
}The adapter enforces Amazon's Seller Central limits while it builds the listing — the title
is cut to 200 characters and the bullets to the first 5, each capped at 500 characters — and
validate_amazon() re-checks the result, which is why a clean run reports
0 constraint issue(s).
adapters/google-merchant/feed.google.tsv — a Google Merchant Center feed, one row per
product (tab-separated, 12 columns; the last two, item_group_id and mpn, are cut from the
snippet below to fit):
id title description link image_link availability price brand condition google_product_category
ACME-HOODIE-07 ACME Heavyweight Fleece Hoodie Heavyweight Fleece Hoodie https://example.com/p/ACME-HOODIE-07 in_stock 59.00 USD ACME new Apparel
ACME-TEE-42 ACME Organic Cotton Crew Tee Organic Cotton Crew Tee https://example.com/p/ACME-TEE-42 in_stock 29.00 USD ACME new Appareladapters/shopify/products.shopify.csv is a Shopify product-import CSV with Shopify's own
column names (Handle, Title, Body (HTML), Vendor, Variant SKU, Variant Price, …).
$ python -m listingforge kit examples/apparel-demo --lang zh
kit for examples/apparel-demo (zh)
localized : 0 field(s) filled in memory
assets :
sku-card 2 cards
social-square 2 cards
detail-page 2 cards
platforms :
amazon 2 listings (0 issue(s))
google-merchant 2-row feed
shopify 2-row csv
→ examples/apparel-demo/kit/zh/ (manifest.json)examples/apparel-demo/kit/zh/
├── manifest.json
├── assets/
│ ├── sku-card/ { index.html, ACME-TEE-42.html, ACME-HOODIE-07.html }
│ ├── social-square/ { index.html, ACME-TEE-42.html, ACME-HOODIE-07.html }
│ └── detail-page/ { index.html, ACME-TEE-42.html, ACME-HOODIE-07.html }
└── platforms/
├── amazon/ { ACME-TEE-42.json, ACME-HOODIE-07.json }
├── google-merchant/ feed.google.tsv
└── shopify/ products.shopify.csv
manifest.json is the machine-readable summary of that run:
{
"example": "apparel-demo",
"lang": "zh",
"products": 2,
"localized_fields": 0,
"assets": { "sku-card": 2, "social-square": 2, "detail-page": 2 },
"platforms": {
"amazon": { "listings": 2, "issues": 0 },
"google-merchant": { "feed_rows": 2 },
"shopify": { "csv_rows": 2 }
}
}(localized_fields is 0 here because the apparel demo already ships bilingual. Running the
same command on examples/fly-demo, where many Chinese fields are missing, fills 199 fields
from the offline glossary and renders 188 products per template.)
$ python -m listingforge localize examples/fly-demo --lang zh --provider glossary --out /tmp/localized
localizing 188 product(s) → zh (provider: GlossaryTranslator)
✓ FD-AC-01.json: filled 1 field(s)
✓ FD-AC-02.json: filled 2 field(s)
...
filled 199 field(s) across 116/188 products → /tmp/localized/
note: offline glossary fills known terms only — product names and marketing copy need `--provider anthropic` (set ANTHROPIC_API_KEY).Localization only ever adds a missing language to a field that already has English — it
never overwrites an existing translation. The three providers layer: a --tm translation
memory takes precedence, then the chosen provider (glossary or Claude).
listingforge/ # engine: model.py (load/validate/i18n), renderer.py, adapters.py, localize.py, cli.py
schema/
product.schema.json # the one contract every template and adapter speaks
templates/
sku-card/ # dense spec card
social-square/ # 1:1 social creative
detail-page/ # tall A+/detail long-page
examples/
apparel-demo/ # 2 products — fully synthetic, fictional "ACME" brand
fly-demo/ # 188 products — anonymized fishing case, fictional "Northbank"
scripts/
ingest_sa.py # vendor parameter files → schema JSON
anonymize.py # de-identify a local dataset into a shippable example
render_png.py # optional: HTML → PNG via Playwright
tests/ # stdlib unittest suite (9 tests)
Generated output (output/, adapters/, kit/, localized/) is git-ignored.
- v0.1.0 preview, plus unreleased work on
main. The v0.1.0 tag ships the engine, schema, 3 templates, 2 platform adapters (Amazon, Google Merchant), localization, the full-kit command, tests and CI. The Shopify adapter is implemented, wired intoadaptandkit, and covered by a test, but has not been tagged in a release yet. - Not on PyPI. Templates, the schema, and the examples live at the repo root, so run from a clone or an editable install. Shipping them as package data is a known follow-up.
examples/apparel-demois fully synthetic (fictional "ACME" brand) — safe to redistribute.examples/fly-demois anonymized: fictional brand ("Northbank"), generic model-number names, genericized technology labels, and scrambled item numbers, produced byscripts/anonymize.py. It keeps the structure — dense parameters, dynamic-column spec tables, bilingual attributes — that makes it a good schema stress test, without any third party's brand or copyrighted copy.- HTML is the only rendering path today; PNG export needs Playwright (
scripts/render_png.py).
Near-term work is deliberately contributor-sized — more platform adapters (TikTok Shop, eBay, Etsy, Temu), more templates (comparison table, hero/banner, print sheet), and more UI languages via translation-memory files. Further out: upstream ingestion of messy vendor data, LLM copy generation beyond translation, an online playground, and a review-mining loop.
Full detail in ROADMAP.md; design rationale in PRD.md; release history in CHANGELOG.md.
pip install -e ".[dev]"
python -m unittest discover -s tests -vCI runs the suite on Python 3.9 / 3.11 / 3.12 plus a build-and-kit smoke test on every push. The architecture is made of small extension points — adding a template, a platform adapter, or an example is usually one new file. See CONTRIBUTING.md.
Apache-2.0 © 2026 The Letusgoshopping Authors.