Iurii RoguliaIurii Rogulia
AboutServicesPricingProjectsStackReviewsPhrasesBlog
Contact
Iurii ships.

Iurii Rogulia, IT partner for business & fractional CTO. Professionally building software since 2001.

Think of a number
PricingQuality checklistPrivacy PolicyCookie Policy

Business

TMI Iurii Rogulia
VAT ID: FI29845875
DUNS: 368664211
Lappeenranta, Finland 🇫🇮

[email protected]
Back to projects

Iurii Localizes: Pikkuna — AI-Powered Localization Pipeline

July 30, 2026

A production localization pipeline built entirely on the OpenAI API: one English source of truth, SEO-aware translation prompts, cross-model verification with an independent model, and a separate AI proofreading agent — all guarded by an automated test suite.

Stack

Python

Libraries

pytestOpenAI SDKpython-dotenvpycountry

Services

OpenAIAnthropic

Topics

AILLMPrompt Engineeringi18nLocalizationSEOAutomationTestingArchitectureCTO

Key Results

  • Keeps 30 locales in sync from a single English source of truth — translators no longer touch JSON by hand
  • Three AI stages on the API: SEO-aware translation, independent cross-model verification, and a separate AI proofreading agent
  • Structural invariants (placeholders, HTML tags, key parity, SEO length limits) enforced by an automated test suite before anything ships
  • Idempotent by design — re-running only fills genuinely missing keys, so cost scales with new content, not catalogue size
Pikkuna — AI-Powered Localization Pipeline

The Business Problem

Pikkuna sells across 30 languages. Every product name, FAQ answer, checkout string, and meta description exists thirty times over, and the source content never stops moving — new products, reworded guarantees, seasonal copy. Keeping thirty locale files consistent with the source by hand is not a translation problem, it is a synchronization problem, and it fails silently: a placeholder dropped in the Estonian file, an HTML tag mangled in Greek, a title tag that runs forty characters too long in German and gets truncated in the SERP. None of that throws an error. It just quietly costs conversions.

The naive fix — "run everything through an LLM" — makes the silent-failure problem worse, not better. A model will happily return fluent text that has quietly lost a {count} placeholder, calqued a technical term, or blown the 60-character title budget that SEO depends on. Fluent and wrong is the most expensive failure mode there is, because nobody reviewing thirty languages will catch it.

So the design goal was never "translate with AI." It was: make the AI output trustworthy enough to ship without a human reading all thirty languages. That constraint is what shaped the whole system.

The Solution

The pipeline treats the OpenAI API as the engine, not an add-on, and wraps it in the guarantees a translation team would otherwise provide manually. English (en.json) is the single source of truth. Everything downstream is derived and disposable.

Three AI stages run per string, each with a distinct job:

  1. Translate — an SEO-aware model call that receives not just the text but the market's keyword spec and hard formatting rules for the key it is translating.
  2. Verify — an independent model (a different vendor, deliberately) grades the translation against the source and flags dropped placeholders, calques, forbidden terms, and length violations. Using the same model to check its own work is worth very little; using a second, unrelated model is where the confidence comes from.
  3. Proofread — a separate AI agent does a final naturalness and grammar pass in the target language, catching the stilted-but-technically-correct phrasing that verification alone won't.

Underneath all three sits an automated test suite that enforces the invariants no model is trusted to hold on its own: placeholder preservation, HTML tag preservation, key parity with the source, and SEO length budgets. If a locale file violates a structural invariant, the build fails — the AI is trusted for language, never for structure.

The whole thing is idempotent. It diffs each locale against the English source, translates only the genuinely missing keys, and removes keys that no longer exist upstream. Re-running is cheap and safe; cost tracks new content, not catalogue size.

Architecture at a Glance

StageEngineResponsibility
Source of truthen.json (per directory)The only file a human edits
DiffDeterministic PythonFind missing keys, drop obsolete ones, preserve existing work
TranslateOpenAI APISEO-aware translation with per-key formatting rules + market keywords
VerifyIndependent model (2nd vendor)Adversarial grading: placeholders, calques, forbidden terms, length
ProofreadAI proofreading agentNaturalness + grammar pass in the target language
Guardrailpytest invariantsStructural correctness — build fails if violated

The division of labour is the point: deterministic code owns structure, AI owns language, and no single model is trusted to grade itself.

Keeping Thirty Locales Legible

A pipeline that runs unattended still needs a human-readable view of where every locale stands. A lightweight status panel renders the pipeline's own state — per locale, per stage — straight from the run metadata it writes: which keys are synced against the English source, which passed independent verification, and which have cleared the proofreading agent.

At a glance it answers the only question a content owner actually has: what can I ship right now? German, French and Swedish sit fully green; Estonian and Polish are mid-proofread; Greek carries a verification flag waiting on a human — all without opening thirty JSON files or reading a single non-English string. The English source stays the one thing anyone edits by hand; everything to the right of it is generated, graded, and reported on.

Under the Hood

One Source of Truth, Diffed — Not Re-Translated

The system never re-translates what already exists. It walks the English source, finds keys missing from each target locale (recursing through nested objects and lists-of-objects alike), and removes keys that have disappeared upstream. Everything already translated is left untouched.

def get_missing_keys(source: dict, target: dict, path: str = "") -> list[tuple[str, Any]]:
    """Keys present in the English source but missing from the target locale."""
    missing = []
    for key in source:
        current = f"{path}.{key}" if path else key
        if key not in target:
            missing.append((current, source[key]))
        elif isinstance(source[key], dict) and isinstance(target[key], dict):
            missing.extend(get_missing_keys(source[key], target[key], current))
        elif isinstance(source[key], list) and isinstance(target[key], list):
            for i, item in enumerate(source[key]):
                if i >= len(target[key]):
                    missing.append((f"{current}.{i}", item))
                elif isinstance(item, dict) and isinstance(target[key][i], dict):
                    missing.extend(get_missing_keys(item, target[key][i], f"{current}.{i}"))
    return missing

This is what makes the pipeline idempotent and cheap to re-run. A copy change to one string re-translates one string, not thirty thousand.

SEO-Aware Prompts — The Key Decides the Rules

Not every string is equal. A body paragraph needs to read naturally; a seo.title has a hard 50–60 character budget and cannot contain separators; an H1 must not duplicate the title tag. The pipeline classifies each key and assembles the system prompt accordingly, injecting the target market's validated keyword spec so the model translates toward the terms buyers actually search for — not a literal calque of the English.

SEO_CRITICAL_PATTERNS = [r"\.seo\.", r"\.seo$", r"\.header$", r"^main\.hero\."]
 
def is_seo_critical(key_path: str) -> bool:
    return any(re.search(p, key_path) for p in SEO_CRITICAL_PATTERNS)
 
def build_system_prompt(target_lang: str, keyword_spec: str | None) -> str:
    prompt = TRANSLATOR_BASE.format(lang=target_lang)
    if keyword_spec:                      # market keywords parsed from SEO docs
        prompt += f"\n\n## MARKET KEYWORDS FOR {target_lang.upper()}\n{keyword_spec}"
    prompt += SEO_FORMAT_RULES            # title length, no separators, H1 ≠ title
    return prompt

The keyword specs are parsed straight out of the project's SEO documentation, so the translator and the SEO strategy never drift apart — the docs are the prompt.

Cross-Model Verification — An Independent Grader

This is the stage that earns the "ship without reading all thirty languages" claim. Every translation is graded by a second model from a different vendor, prompted adversarially: its job is to find the defect, not to approve. It checks that every placeholder and HTML tag survived, that no forbidden term or calque slipped in, and that SEO length budgets held. A model checking its own output tends to rationalize it; an independent model has no such attachment.

def verify_translation(source: str, translation: str, key_path: str, target_lang: str) -> Verdict:
    """Grade a translation with an independent model. Returns pass/fail + reasons."""
    review = anthropic.messages.create(
        model="claude-sonnet-5",
        system=VERIFIER_SYSTEM,   # adversarial: assume it's wrong, prove otherwise
        messages=[{
            "role": "user",
            "content": VERIFY_TEMPLATE.format(
                source=source, translation=translation,
                key=key_path, lang=target_lang,
                placeholders=extract_placeholders(source),
            ),
        }],
    )
    return Verdict.parse(review.content[0].text)   # {ok: bool, issues: [...]}

Anything the verifier fails is routed back for a re-translate with the verifier's notes appended to the prompt — a tight correction loop that converges instead of guessing.

The AI Proofreading Agent

Verification catches defects. It does not catch stiffness — a translation that is accurate, structurally perfect, and still reads like a machine wrote it. A separate proofreading agent handles that: a final API pass whose only mandate is naturalness and grammar in the target language, forbidden from changing meaning, placeholders, or markup. It is the difference between "correct" and "a native speaker wrote this."

def proofread(translation: str, target_lang: str) -> str:
    """Naturalness + grammar pass. Must preserve meaning, placeholders and tags."""
    out = openai.chat.completions.create(
        model="gpt-4o",
        messages=[
            {"role": "system", "content": PROOFREADER_SYSTEM.format(lang=target_lang)},
            {"role": "user", "content": translation},
        ],
        temperature=0.2,
    )
    return out.choices[0].message.content

Keeping proofreading as its own agent — rather than folding it into the translation prompt — matters: a single prompt asked to translate and polish and respect SEO limits does all three worse. Narrow agents with one job each are more reliable and far easier to test.

The Test Suite — Structure Is Never Trusted to the Model

The AI owns language. It is never trusted with structure. Those invariants are enforced by a pytest suite that runs over the generated locale files and fails the build on any violation.

import pytest
 
LOCALES = load_target_locales()          # 30 target languages
 
@pytest.mark.parametrize("locale", LOCALES)
def test_placeholders_preserved(locale):
    for key, src in flatten(SOURCE).items():
        tgt = flatten(load(locale)).get(key)
        assert extract_placeholders(src) == extract_placeholders(tgt), \
            f"{locale}:{key} — placeholder set changed"
 
@pytest.mark.parametrize("locale", LOCALES)
def test_html_tags_preserved(locale):
    for key, src in flatten(SOURCE).items():
        tgt = flatten(load(locale)).get(key)
        assert extract_tags(src) == extract_tags(tgt), f"{locale}:{key} — tags changed"
 
@pytest.mark.parametrize("locale", LOCALES)
def test_key_parity(locale):
    assert set(flatten(load(locale))) == set(flatten(SOURCE)), f"{locale} — key drift"
 
@pytest.mark.parametrize("locale", LOCALES)
def test_seo_title_length(locale):
    for key, val in flatten(load(locale)).items():
        if key.endswith("seo.title"):
            assert 50 <= len(val) <= 60, f"{locale}:{key} — title {len(val)} chars"

A dropped {count}, a mangled <link>, a key that drifted out of parity, a German title that ran long — all of it fails loudly at build time instead of failing silently in production. That is the whole reason the pipeline can be trusted to run without a human reviewing thirty languages by hand.

Why This Is the Interesting Part

E-commerce plumbing — payments, PDFs, shipping — is well-trodden. What made this build worth writing up is the discipline around the AI itself. The trap with LLM features is treating the model as an oracle: prompt it, trust the output, ship. This pipeline does the opposite. It uses three separate AI stages, each narrow, plays two vendors against each other so no model grades its own work, and backs the whole thing with deterministic tests that treat every model as fallible. That is what turns "we call an API" into a system you can actually depend on — and it is the pattern I now reach for on any AI integration, not just localization.

Iurii RoguliaAvailable

Need something similar?

I build custom solutions — from APIs to full products. Let's talk about your project.

View all projects

Related projects

Pikkuna — E-commerce for Vinyl Curtains & PVC Products
Pikkuna — E-commerce for Vinyl Curtains & PVC Products
October 12, 2024
Pikkuna — E-commerce for Vinyl Curtains & PVC Products

International e-commerce platform with 30 locales, product configurators, AI chatbot, and fully automated order flow: Stripe → Zoho CRM → Airtable → Mailgun →

Stack

Next.jsReactTypeScript

Libraries

next-intlZodpdf-libPuppeteerStripe.js

Databases

Redis

Services

StripeZohoMailgunPostNordNetvisorVercelVercel BlobGoogle AnalyticsMeta CAPIAirtableUpstash

Topics

E-commercePaymentsShippingPDFPWACTOArchitectureSEOSchema.orgi18nPerformance
pi-pi.ee — B2B E-commerce for Waterless Urinal Systems
pi-pi.ee — B2B E-commerce for Waterless Urinal Systems
January 17, 2026
pi-pi.ee — B2B E-commerce for Waterless Urinal Systems

i18n B2B e-commerce platform for waterless urinal products across 32 European countries with automated VAT handling, PDF invoicing, and CRM integration.

Stack

Next.jsReactTypeScriptTailwind CSS

Libraries

next-intlDaisyUIStripe SDKreact-pdf

Services

StripeVercelResendNotionGoogle Analytics

Topics

E-commerceB2Bi18nPDFSSRArchitectureSEOSchema.org

Related posts

How to Reduce OpenAI API Costs in Production (2026 Guide)
June 15, 2026· 24 min
How to Reduce OpenAI API Costs in Production (2026 Guide)

OpenAI API cost optimization: where the money actually goes in production, prompt caching, model routing, budget controls, and observability that surfaces

Topics

AIOpenAICost OptimizationProductionObservabilityArchitecture
AI Document Processing in Production: Full Pipeline Guide
May 7, 2026· 14 min
AI Document Processing in Production: Full Pipeline Guide

AI document processing in production: full PDF pipeline — OCR fallbacks, structured output, validation, cost at scale. Beyond naive GPT calls.

Stack

TypeScriptPython

Services

OpenAIAWS TextractGoogle Document AI

Topics

PDFDocument ProcessingArchitectureSaaS