Cookbooks
Complete, live-run recipes. Each translates only text-like columns, protects
placeholders, and returns a structured TranslationResult.
Translate a catalog with OpenAI
Section titled “Translate a catalog with OpenAI”Translate the text columns of a catalog with a hosted OpenAI model, leaving IDs, prices, and codes untouched.
import osfrom csv_trans import translatefrom csv_trans.providers import OpenAICompatibleProvider
openai = OpenAICompatibleProvider( model="gpt-4o-mini", base_url="https://api.openai.com/v1", api_key=os.environ["OPENAI_API_KEY"],)
result = translate( "catalog.csv", "en", "es", provider=openai, columns=["text"], output_path="catalog.es.csv", overwrite=True,)print(result.status.value, "translated:", result.translated_cells, "cached:", result.cached_cells)Given this input:
id,text,code1,Hello world,SKU-12,Hello world,SKU-23,Good morning,SKU-3the output preserves id and code exactly and translates only text:
id,text,code1,Hola mundo,SKU-12,Hola mundo,SKU-23,Buenos días,SKU-3result.cached_cells is 1 here: "Hello world" appears twice but is sent to
the model once and reused. On a 200-row file with ~10 distinct phrases, this
turns 200 cells into ~10 API calls.
Let the engine pick the columns
Section titled “Let the engine pick the columns”Omit columns to auto-select. Numeric, identifier, URL/date, and
credential-named columns are skipped, so secrets are never sent:
result = translate("catalog.csv", "en", "es", provider=openai, output_path="catalog.es.csv", overwrite=True)print([c.name for c in result.selected_columns if c.selected])For a client_secret,greeting,amount header this selects only greeting;
client_secret and amount are copied through untouched. See
Column selection.
Placeholders survive
Section titled “Placeholders survive”URLs and tokens are held back locally and reinserted byte-for-byte, so
"Visit https://ex.com for {{n}} items" becomes
"Visitar https://ex.com para {{n}} artículos" — the URL and {{n}} are
identical in the output. See
Placeholder protection.
Translate confidential data locally
Section titled “Translate confidential data locally”Route translation through a local model server (Ollama, vLLM, LM Studio, or
llama.cpp) and set privacy="local-only" so no cell can leave the machine. The
mode validates every endpoint as loopback before any text is submitted.
from csv_trans import translate_csv, TranslationConfigfrom csv_trans.providers import OpenAICompatibleProvider
ollama = OpenAICompatibleProvider( model="qwen2.5:14b-instruct", base_url="http://localhost:11434/v1", # Ollama's OpenAI-compatible endpoint api_key=None, # local servers usually need no key timeout=240.0,)
config = TranslationConfig( source_language="en", target_language="es", provider=ollama, columns=["CIPTitle"], privacy="local-only",)
result = translate_csv("cip_taxonomy.csv", config, output_path="cip_taxonomy.es.csv")print(result.status.value, result.translated_cells)Run against a real public taxonomy (US CIP program codes), this translates the
CIPTitle column while preserving the spreadsheet-guarded codes exactly:
CIPCode,CIPTitle="01.0000","AGRICULTURAL/ANIMAL/PLANT/VETERINARY SCIENCE AND RELATED FIELDS."="01.0101","Agriculture, General."becomes
CIPCode,CIPTitle="01.0000","CIENCIA AGRÍCOLA/ANIMAL/PLANTA/VETERINARIA Y RAMAS AFINES."="01.0101","Agricultura, general."The ="01.0000" Excel text-guards pass through byte-for-byte — the engine never
reinterprets field values.
The boundary is enforced, not advisory
Section titled “The boundary is enforced, not advisory”local-only rejects a remote endpoint before contacting it:
from csv_trans import PrivacyViolation
remote = OpenAICompatibleProvider(model="gpt-4o-mini", base_url="https://api.openai.com/v1", api_key="sk-...")try: translate_csv("cip_taxonomy.csv", TranslationConfig( source_language="en", target_language="es", provider=remote, privacy="local-only", ), output_path="out.csv")except PrivacyViolation as exc: print(exc) # "local-only mode rejected openai-compatible: ..."A non-loopback host on the LAN can be allowed explicitly with
approved_local_hosts=("model.internal",). See
Privacy modes for the exact rules and their
limits.
Quick translation with no API key
Section titled “Quick translation with no API key”GoogleFreeProvider needs no credentials, which makes it convenient for quick,
non-sensitive jobs.
from csv_trans import translatefrom csv_trans.providers import GoogleFreeProvider
result = translate( "messages.csv", "en", "es", provider=GoogleFreeProvider(), columns=["text"], output_path="messages.es.csv", overwrite=True,)Placeholders are protected here too — "Hello {{name}}, good morning" becomes
"Hola {{name}}, buen día", with {{name}} unchanged.
This is also the provider used when you set no provider at all
(provider=None), which is why the CLI prints a disclosure warning on that path.
Fallbacks and partial results
Section titled “Fallbacks and partial results”A run never silently drops a cell. Transient errors retry, and only unresolved items move to a fallback provider; a cell that still fails keeps its complete original value and is reported.
Add a fallback provider
Section titled “Add a fallback provider”Unresolved items move to fallback_providers in order. Here OpenAI is primary
and the no-key Google adapter is the backup:
import osfrom csv_trans import translate_csv, TranslationConfigfrom csv_trans.providers import OpenAICompatibleProvider, GoogleFreeProvider
config = TranslationConfig( source_language="en", target_language="es", provider=OpenAICompatibleProvider( model="gpt-4o-mini", base_url="https://api.openai.com/v1", api_key=os.environ["OPENAI_API_KEY"], ), fallback_providers=(GoogleFreeProvider(),), columns=["text"], report_path="run.report.json",)
result = translate_csv("catalog.csv", config, output_path="catalog.es.csv", overwrite=True)print(result.status.value, "fallbacks:", result.fallbacks)Privacy is re-checked before every provider call, so a fallback that would cross your privacy boundary is rejected rather than used.
Handle a partial run
Section titled “Handle a partial run”status is partial when one or more cells were preserved rather than
translated. Those cells hold their original value in the output CSV, and each
appears in failures:
if result.status.value == "partial": for failure in result.failures: print(f"row {failure.row} col {failure.column_index}: {failure.category} — {failure.message}")Failure message and category are safe, category-derived strings — never the
source cell text or a credential. See
Failure recovery and
TranslationResult.
Exit codes for scripts
Section titled “Exit codes for scripts”The CLI mirrors this: 0 for success, 2 for a partial
result (output written, some cells preserved), 1 for a fatal error.