3 minutes
Translation & Localization with Offline LLMs
Introduction
I work with international teams and projects that need UI strings, docs, or blog posts translated without sending my proprietary text to a cloud API. Running a local translation model gives me full control, consistent phrasing, and zero per-request costs. Here’s how I set up my offline translation and basic localization memory.
Why Offline Translation Works for Me
- Data Privacy: No chance of leaking internal UI strings or unreleased docs.
- Consistency: I can build or reuse a local translation memory (TM) to enforce consistent terminology.
- Cost Efficiency: Once downloaded, the model produces translations without paying per-token fees.
Pipeline Overview
- Choose a Multilingual Model – Picks like
Helsinki-NLP/opus-mt-*
orfacebook/mbart-large-50
for many language pairs. - Set Up Translation Memory (TM) – Store key-value pairs of original↔translated strings in SQLite or JSON.
- Translate with Transformers Pipeline – Use HF’s
pipeline('translation_xx_to_yy')
locally. - Post-Process & Integrate – Apply TM overrides, handle placeholders, and export back to resource files.
I’ll walk through each step with code snippets and notes on what tweaks I made.
1. Picking & Loading the Model
I stick with the OPUS-MT family from Helsinki-NLP for quick downloads and moderate quality. For Spanish to English:
pip install transformers sentencepiece
from transformers import pipeline
# Initialize translator for Es→En
translator = pipeline(
'translation_es_to_en',
model='Helsinki-NLP/opus-mt-es-en',
device=0 # GPU if available, else remove
)
# Quick test
test = translator("¿Cómo estás?", max_length=40)
print(test[0]['translation_text']) # "How are you?"
If I need multiple pairs, I create a dict of pipelines keyed by language codes.
2. Building a Simple Translation Memory
Why: Enforce consistent terminology (e.g., product names, legal phrases).
I use SQLite since it’s lightweight and supports fuzzy lookups via LIKE
or the fuzzystrmatch
extension.
import sqlite3
conn = sqlite3.connect('tm.db')
cur = conn.cursor()
# Create table
cur.execute('''
CREATE TABLE IF NOT EXISTS tm (
source TEXT PRIMARY KEY,
target TEXT
);
''')
conn.commit()
# Example insert
cur.execute("INSERT OR IGNORE INTO tm (source, target) VALUES (?, ?)",
("Cloud Provider", "Proveedor de nube"))
conn.commit()
Lookup function:
def lookup_tm(text):
cur.execute("SELECT target FROM tm WHERE source = ?", (text,))
row = cur.fetchone()
return row[0] if row else None
Before calling the model, I check each string against the TM and use the stored translation if available.
3. Translating & Merging with TM
My Workflow: I load resource files (JSON/YAML), iterate over key/value pairs, lookup TM first, else call the model, then save both output and new TM entries.
import json
# Load strings
with open('strings_es.json') as f:
strings = json.load(f)
translated = {}
for key, src in strings.items():
# Check TM
tm_hit = lookup_tm(src)
if tm_hit:
translated[key] = tm_hit
else:
out = translator(src, max_length=200)[0]['translation_text']
translated[key] = out
# Save new TM entry
cur.execute("INSERT INTO tm (source, target) VALUES (?, ?)", (src, out))
conn.commit()
# Write translated resource
with open('strings_en.json', 'w') as f:
json.dump(translated, f, ensure_ascii=False, indent=2)
I usually run this as python translate.py strings_es.json
and inspect diffs before committing.
4. Post-Processing & Placeholder Handling
Many strings include placeholders ({username}
, %s
, etc.). I protect them by replacing before translation:
import re
def mask_placeholders(text):
return re.sub(r"\{[^}]+\}", "<PH>", text)
def unmask_placeholders(translated, original):
# simple sequential replacement
orig_ph = re.findall(r"\{[^}]+\}", original)
for ph in orig_ph:
translated = translated.replace("<PH>", ph, 1)
return translated
# Example usage:
raw = "Hello, {username}!"
masked = mask_placeholders(raw)
out = translator(masked)[0]['translation_text']
final = unmask_placeholders(out, raw)
print(final) # "Hello, {username}!"
This preserves dynamic parts perfectly.
Wrapping Up
That’s my offline translation and simple TM flow. It’s not foolproof for nuanced localization, but it’s quick to set up, fully private, and keeps terminology consistent. Next, I’m planning to pipeline audio transcriptions into this system so I can localize subtitles from meeting recordings.