Automate AI Translation with n8n + Ollama (Self-Hosted Content Localization Workflow)

Published March 24, 2026 · 14 min read · By Workflow Forge

Expanding into international markets is one of the highest-ROI moves a business can make. Harvard Business Review research shows that 72% of consumers spend most of their time on websites in their own language, and 56% say the ability to read product information in their native language is more important than price.

But translation is expensive. Professional human translation costs $0.10–0.30 per word. A 2,000-word product page translated into 5 languages runs $1,000–3,000. An e-commerce store with 500 products? That’s $50,000–150,000 just for the initial translation — before you factor in ongoing updates every time you change a description, launch a new product, or update your marketing copy.

Generic machine translation (Google Translate, DeepL) is cheaper but misses the mark on brand voice, technical terminology, and cultural context. “Our cloud-native platform enables teams to ship faster” becomes awkward and unnatural in most languages when run through a generic translator.

With n8n and Ollama, you can build a self-hosted AI translation pipeline that delivers context-aware, brand-consistent translations — running entirely on your own hardware, with zero per-word costs and full data privacy.

What you’ll build: An n8n translation automation workflow that ingests content via webhook or CMS integration, translates it into multiple target languages with glossary enforcement and style control, validates quality with a second AI pass, and outputs production-ready localized content. All powered by Ollama — no API keys, no cloud dependencies, no per-character charges.

Why Self-Hosted Translation Beats Cloud APIs

Before building, here’s why running your own Ollama translation pipeline makes sense compared to paid translation APIs:

FeatureGoogle Translate APIDeepL APIn8n + Ollama (This Workflow)
Cost per 1M characters$20$25$0 (local)
500 products × 5 languages~$500~$625$0
Custom glossary/terminologyLimited (500 terms max)Basic (paid tiers only)Unlimited, full control
Context-awarenessSentence-levelParagraph-levelDocument-level + custom context
Tone/style controlNoneFormal/Informal onlyAny style via prompt
Data privacySent to Google serversSent to DeepL servers100% on your hardware
Rate limitsYes (600K chars/min)Yes (varies by plan)None — limited only by your CPU/GPU
Works offlineNoNoYes
Privacy matters for translation: Product descriptions, internal communications, legal documents, customer support templates, and marketing copy often contain proprietary information, pricing strategies, or customer data. Sending this to third-party APIs creates compliance risk under GDPR, CCPA, and industry regulations. Local processing with Ollama keeps everything on your infrastructure.

Prerequisites

Pull a translation model:

# Best all-around for European + major Asian languages:
ollama pull mistral:7b

# Best for Chinese, Japanese, Korean:
ollama pull qwen2:7b

# Best quality (needs 20GB+ RAM):
ollama pull command-r:35b

Architecture Overview

Content Source (Webhook / CMS API / File / Database)
    ↓
[Pre-Process] — Detect language, segment into translatable chunks
    ↓
[Load Glossary] — Fetch brand terminology from Google Sheets / JSON
    ↓
[Translate] — Ollama generates context-aware translation per segment
    ↓
[Validate] — Second AI pass checks accuracy, glossary compliance, completeness
    ↓                          ↓ (if fails)
    ↓                   [Re-translate with feedback]
    ↓
[Reassemble] — Merge segments, format output
    ↓
[Output] — Update CMS, write i18n files, export CSV, send via webhook

Step-by-Step: Build the Translation Workflow

STEP 1 — Content Input

Set up a webhook trigger to accept translation requests

Create a Webhook node in n8n that accepts POST requests with the content to translate, target languages, and optional context:

// Webhook payload structure
POST /webhook/translate
{
  "source_text": "Our cloud-native platform enables teams to ship faster with built-in CI/CD pipelines and automated testing frameworks.",
  "source_language": "en",
  "target_languages": ["de", "fr", "es", "ja", "pt-BR"],
  "context": "SaaS marketing website - hero section",
  "tone": "professional but approachable",
  "content_type": "marketing",
  "glossary": {
    "cloud-native": {"de": "Cloud-nativ", "fr": "cloud-native", "es": "nativo en la nube", "ja": "クラウドネイティブ"},
    "CI/CD": {"all": "CI/CD"},
    "pipelines": {"de": "Pipelines", "fr": "pipelines", "ja": "パイプライン"}
  }
}

The context and tone fields are what separate this from generic machine translation. Telling the model that this is a “SaaS marketing hero section” produces dramatically different output than “technical documentation” or “casual blog post.”

STEP 2 — Pre-Processing and Segmentation

Split content into optimal chunks for translation quality

Long texts need to be segmented before translation. Too-long inputs reduce quality; too-short inputs lose context. The sweet spot is 200–500 words per segment.

// n8n Function node: segment content for translation
const text = $input.first().json.source_text;
const targetLangs = $input.first().json.target_languages;
const segments = [];

// Split by paragraphs first, then by sentences if too long
const paragraphs = text.split(/\n\n+/);
for (const para of paragraphs) {
  if (para.length > 500) {
    // Split long paragraphs into sentence groups
    const sentences = para.match(/[^.!?]+[.!?]+/g) || [para];
    let chunk = '';
    for (const sent of sentences) {
      if ((chunk + sent).length > 500 && chunk) {
        segments.push(chunk.trim());
        chunk = sent;
      } else {
        chunk += sent;
      }
    }
    if (chunk) segments.push(chunk.trim());
  } else {
    segments.push(para);
  }
}

// Create one item per segment per language
const items = [];
for (const lang of targetLangs) {
  for (let i = 0; i < segments.length; i++) {
    items.push({
      json: {
        segment: segments[i],
        segment_index: i,
        total_segments: segments.length,
        target_language: lang,
        source_language: $input.first().json.source_language,
        context: $input.first().json.context,
        tone: $input.first().json.tone,
        glossary: $input.first().json.glossary || {}
      }
    });
  }
}

return items;

This creates one work item per segment per language. For a 3-paragraph text going into 5 languages, that’s 15 items — each processed independently, which enables parallel translation.

STEP 3 — Glossary Preparation

Build language-specific glossary instructions for each segment

The glossary is the secret weapon for consistent brand translation. This node formats glossary entries for the target language and embeds them in the prompt:

// n8n Function node: prepare glossary for prompt
const glossary = $json.glossary;
const targetLang = $json.target_language;
const entries = [];

for (const [term, translations] of Object.entries(glossary)) {
  const translation = translations[targetLang] || translations['all'] || null;
  if (translation) {
    entries.push(`"${term}" → "${translation}"`);
  }
}

const glossaryText = entries.length > 0
  ? `\n\nGLOSSARY (use these exact terms in your translation):\n${entries.join('\n')}`
  : '';

return [{
  json: {
    ...$json,
    glossary_prompt: glossaryText
  }
}];

For large glossaries (100+ terms), you can fetch them from a Google Sheet or database at the start of the workflow instead of passing them in every request. Use an HTTP Request node to hit the Google Sheets API or a Postgres/MySQL node to query your terminology database.

STEP 4 — AI Translation with Ollama

The core translation node with context-aware prompting

This is where the actual translation happens. The prompt includes context, tone, glossary, and specific rules to preserve formatting:

// HTTP Request node to Ollama
POST http://localhost:11434/api/generate

{
  "model": "mistral:7b",
  "prompt": "You are an expert human translator specializing in {{ $json.content_type || 'general' }} content. Translate the following text from {{ $json.source_language }} to {{ $json.target_language }}.\n\nCONTEXT: {{ $json.context }}\nTONE: {{ $json.tone }}\n{{ $json.glossary_prompt }}\n\nRULES:\n1. Preserve all HTML tags, URLs, email addresses, and code snippets exactly as-is\n2. Use glossary terms exactly as specified — do not paraphrase them\n3. Match the tone and register described above\n4. Preserve paragraph structure, bullet points, and formatting\n5. Adapt idioms and cultural references naturally — do not translate them literally\n6. Keep proper nouns, brand names, and product names in their original form unless glossary specifies otherwise\n7. Output ONLY the translated text — no explanations, no notes, no alternatives\n\nSOURCE TEXT:\n{{ $json.segment }}\n\nTRANSLATION:",
  "stream": false,
  "options": {
    "temperature": 0.3,
    "num_predict": 2048,
    "top_p": 0.9
  }
}
Why temperature: 0.3? Translation needs to be accurate and consistent, not creative. Low temperature reduces randomness, meaning the same input produces nearly identical output each time. This is critical for brand consistency — you don’t want “Sign Up” translated differently on every page. For marketing copy where you want more natural-sounding variations, you can bump this to 0.4–0.5.
STEP 5 — Quality Validation (Second AI Pass)

Automatically catch translation errors before they reach production

A separate Ollama call reviews each translation for accuracy, completeness, and glossary compliance. This catches the errors that would otherwise require a human reviewer:

// HTTP Request node: quality validation
POST http://localhost:11434/api/generate

{
  "model": "mistral:7b",
  "prompt": "You are a professional translation quality reviewer. Compare the source text and its translation below.\n\nSource ({{ $json.source_language }}):\n{{ $json.segment }}\n\nTranslation ({{ $json.target_language }}):\n{{ $json.translated_text }}\n\nRequired glossary terms:\n{{ $json.glossary_prompt }}\n\nExpected tone: {{ $json.tone }}\n\nEvaluate the translation on these criteria (score 0-100 each):\n1. COMPLETENESS: Is all source content present in the translation?\n2. ACCURACY: Does the translation preserve the original meaning?\n3. GLOSSARY: Are all required glossary terms used correctly?\n4. NATURALNESS: Does it read naturally in the target language?\n5. TONE: Does it match the expected tone/register?\n\nRespond in valid JSON only:\n{\"completeness\": N, \"accuracy\": N, \"glossary\": N, \"naturalness\": N, \"tone\": N, \"overall\": N, \"issues\": [\"description of any problems\"], \"pass\": true_or_false}",
  "stream": false,
  "options": {
    "temperature": 0.1,
    "num_predict": 512
  }
}

After the validation, a Function node parses the JSON response and routes the result:

// Parse validation result and decide next step
const response = JSON.parse($json.response);
let quality;

try {
  quality = JSON.parse(response);
} catch (e) {
  // If model output isn't valid JSON, flag for re-translation
  quality = { pass: false, overall: 0, issues: ['Invalid validation response'] };
}

const result = {
  ...$input.first().json,
  quality_score: quality.overall,
  quality_details: quality,
  needs_retranslation: !quality.pass || quality.overall < 75
};

return [{ json: result }];

If needs_retranslation is true, the workflow loops back to Step 4 with the issues appended to the prompt as additional context. This self-correction catches 90%+ of quality issues automatically.

STEP 6 — Reassembly and Output

Merge translated segments and deliver results

// n8n Function node: reassemble translated segments
const items = $input.all();
const translations = {};

for (const item of items) {
  const lang = item.json.target_language;
  if (!translations[lang]) {
    translations[lang] = {
      segments: [],
      quality_scores: []
    };
  }
  translations[lang].segments[item.json.segment_index] = item.json.translated_text;
  translations[lang].quality_scores.push(item.json.quality_score);
}

// Build final output
const output = {};
for (const [lang, data] of Object.entries(translations)) {
  const avgQuality = data.quality_scores.reduce((a, b) => a + b, 0) / data.quality_scores.length;
  output[lang] = {
    translated_text: data.segments.join('\n\n'),
    average_quality_score: Math.round(avgQuality),
    word_count: data.segments.join(' ').split(/\s+/).length,
    segment_count: data.segments.length
  };
}

return [{
  json: {
    source_language: items[0].json.source_language,
    translations: output,
    translated_at: new Date().toISOString()
  }
}];

Real-World Use Cases

E-Commerce Product Localization

Translate product titles, descriptions, specifications, and category pages for international storefronts. Connect to Shopify (Admin API + Markets), WooCommerce (REST API + WPML), or Magento to automatically translate new products as they’re added. A store with 500 products can have all 5 target languages completed overnight.

SaaS App Localization (i18n Files)

Feed your en.json, .po, .xliff, or .strings files through the workflow. The translation preserves JSON structure, placeholder tokens ({{username}}, %d items), and HTML tags. Output files are ready to deploy directly into your app’s locale directory.

// Example: translating i18n JSON keys
// Input (en.json):
{
  "nav.dashboard": "Dashboard",
  "nav.settings": "Settings",
  "welcome.title": "Welcome back, {{name}}!",
  "items.count": "You have {{count}} items in your cart"
}

// Output (de.json) - preserves keys and placeholders:
{
  "nav.dashboard": "Dashboard",
  "nav.settings": "Einstellungen",
  "welcome.title": "Willkommen zurück, {{name}}!",
  "items.count": "Sie haben {{count}} Artikel in Ihrem Warenkorb"
}

Marketing Email Campaigns

Automatically translate email templates before each campaign send. The workflow preserves HTML formatting, personalization tokens, CTA buttons, and tracking links. Schedule translations to run 24 hours before send time so your team can review before launch.

Documentation and Knowledge Base

Translate help articles, API documentation, and knowledge base content. The context-awareness handles technical terminology that generic translators consistently get wrong — “endpoint” stays “Endpoint” in German (not “Endpunkt”), “deploy” becomes the correct localized term in each market.

Automated Content Pipeline

Combine the translation workflow with the Social Media Content Generator — write content once in English, then automatically translate and publish to localized social accounts across all markets.

Best Ollama Models for Translation

ModelSizeBest ForStrong LanguagesSpeed (words/min)
mistral:7b4.1 GBEuropean languages, marketing contentEN, DE, FR, ES, IT, PT, NL~200
llama3:8b4.7 GBBroad language support, technical docs30+ languages, strong EN/ES/DE/FR~180
qwen2:7b4.4 GBChinese, Japanese, Korean contentZH, JA, KO + EN~170
command-r:35b20 GBHighest quality, complex content50+ languages including low-resource~80
gemma2:9b5.4 GBGood balance of quality and speedEN, DE, FR, ES, IT, JA, KO, ZH~160
Model selection tip: For European language pairs, mistral:7b delivers the best quality-to-speed ratio. For CJK languages (Chinese, Japanese, Korean), qwen2:7b is significantly better than Western-focused models. If you need the highest quality across all languages and have 20GB+ RAM, command-r:35b is the clear choice.

Performance and Throughput

Benchmarks (16GB RAM, no dedicated GPU):
  • mistral:7b: ~200 words/minute per language
  • llama3:8b: ~180 words/minute per language
  • With quality validation (2 passes): ~120 words/minute
  • 5 languages sequentially: ~600 words/minute total throughput

Real-world estimates:

  • 2,000-word blog post → 5 languages: ~15 minutes
  • 500 product descriptions (50 words each) → 5 languages: ~3.5 hours
  • Full SaaS app (2,000 i18n strings) → 5 languages: ~2 hours
  • 10,000-word documentation site → 5 languages: ~75 minutes

With a GPU (RTX 3060+), multiply throughput by 3–5x.

Advanced: Centralized Glossary Management

For organizations translating consistently across multiple projects, maintain a centralized glossary in Google Sheets or a database. The workflow fetches the latest terms at the start of each run:

// Fetch glossary from Google Sheets (first node in workflow)
// Sheet columns: term_en | term_de | term_fr | term_es | term_ja | notes
GET https://sheets.googleapis.com/v4/spreadsheets/{{sheet_id}}/values/Glossary!A:G

// Or maintain a local glossary.json file:
{
  "platform": {"de": "Plattform", "fr": "plateforme", "es": "plataforma", "ja": "プラットフォーム"},
  "dashboard": {"de": "Dashboard", "fr": "tableau de bord", "es": "panel de control"},
  "workflow": {"de": "Workflow", "fr": "flux de travail", "es": "flujo de trabajo"},
  "deploy": {"de": "bereitstellen", "fr": "déployer", "es": "desplegar"},
  "Sign Up": {"de": "Registrieren", "fr": "S'inscrire", "es": "Regístrate"}
}

This ensures that “Sign Up” is always “Registrieren” in German across every page, email, and app screen — something generic translation APIs cannot guarantee.

Integration with CMS Platforms

Connect the translation workflow to your content management system for fully automated localization on content publish:

Get the Complete Translation Workflow + 10 More AI Templates

This n8n translation automation workflow is included in our Self-Hosted AI Workflow Pack with 11 production-ready n8n templates — all powered by Ollama, no API keys needed.

Includes: translation pipeline, email responder, blog writer, social content generator, document summarizer, meeting notes, lead scoring, code review, and more.

Get All 11 Workflows — $39

One-time payment. No subscription. 30-day money-back guarantee.

Free Starter Workflow (Import into n8n)

Here’s a simplified version of the automated content localization workflow you can import directly into n8n. It handles single-language translation with basic quality validation:

Click to expand free workflow JSON
{
  "name": "AI Translation Pipeline - Ollama (Free Starter)",
  "nodes": [
    {
      "name": "Webhook Trigger",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [250, 300],
      "parameters": {
        "path": "translate",
        "httpMethod": "POST",
        "responseMode": "lastNode"
      }
    },
    {
      "name": "Prepare Translation",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [450, 300],
      "parameters": {
        "assignments": {
          "assignments": [
            {"name": "source_text", "value": "={{ $json.body.source_text }}", "type": "string"},
            {"name": "source_language", "value": "={{ $json.body.source_language || 'en' }}", "type": "string"},
            {"name": "target_language", "value": "={{ $json.body.target_language || 'de' }}", "type": "string"},
            {"name": "context", "value": "={{ $json.body.context || 'general content' }}", "type": "string"},
            {"name": "tone", "value": "={{ $json.body.tone || 'professional' }}", "type": "string"}
          ]
        }
      }
    },
    {
      "name": "Translate with Ollama",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [650, 300],
      "parameters": {
        "url": "http://localhost:11434/api/generate",
        "method": "POST",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({model: 'mistral:7b', prompt: 'You are an expert translator. Translate the following from ' + $json.source_language + ' to ' + $json.target_language + '.\\n\\nContext: ' + $json.context + '\\nTone: ' + $json.tone + '\\n\\nRules: Preserve all HTML/URLs/code. Output ONLY the translation.\\n\\nText: ' + $json.source_text + '\\n\\nTranslation:', stream: false, options: {temperature: 0.3, num_predict: 2048}}) }}",
        "options": {"timeout": 120000}
      }
    },
    {
      "name": "Extract Translation",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [850, 300],
      "parameters": {
        "assignments": {
          "assignments": [
            {"name": "translated_text", "value": "={{ JSON.parse($json.data).response.trim() }}", "type": "string"},
            {"name": "source_text", "value": "={{ $('Prepare Translation').item.json.source_text }}", "type": "string"},
            {"name": "source_language", "value": "={{ $('Prepare Translation').item.json.source_language }}", "type": "string"},
            {"name": "target_language", "value": "={{ $('Prepare Translation').item.json.target_language }}", "type": "string"}
          ]
        }
      }
    }
  ],
  "connections": {
    "Webhook Trigger": {"main": [[{"node": "Prepare Translation", "type": "main", "index": 0}]]},
    "Prepare Translation": {"main": [[{"node": "Translate with Ollama", "type": "main", "index": 0}]]},
    "Translate with Ollama": {"main": [[{"node": "Extract Translation", "type": "main", "index": 0}]]}
  },
  "settings": {"executionOrder": "v1"},
  "tags": [{"name": "AI"}, {"name": "Ollama"}, {"name": "Translation"}]
}

How to Use the Free Workflow

  1. Copy the JSON above and import via n8n → Workflows → Import from JSON
  2. Make sure Ollama is running: ollama serve
  3. Pull the model: ollama pull mistral:7b
  4. If n8n runs in Docker, replace localhost with host.docker.internal in the Ollama URL
  5. Activate the workflow and test with a POST request:
    curl -X POST http://localhost:5678/webhook/translate \
      -H "Content-Type: application/json" \
      -d '{
        "source_text": "Welcome to our platform. Get started in minutes.",
        "source_language": "en",
        "target_language": "de",
        "context": "SaaS onboarding page",
        "tone": "friendly and professional"
      }'

Tips for Best Translation Quality

  1. Always provide context — “marketing hero section” vs “API documentation” vs “customer support email” produces very different translations from the same source text. This single change improves output quality more than any model upgrade.
  2. Use glossaries for brand consistency — Without explicit glossary terms, the model will translate “Dashboard” differently each time (German: “Dashboard,” “Übersicht,” “Instrumententafel”). Pin your terms.
  3. Keep segments at 200–500 words — Too short (under 50 words) loses context. Too long (over 1,000 words) increases errors and hallucination. The segmentation in Step 2 handles this automatically.
  4. Always validate with a second pass — The quality check in Step 5 catches missing content, wrong glossary terms, and tone mismatches before they reach production. Skip this only for low-stakes content.
  5. Match models to language pairs — Use qwen2 for CJK, mistral for European languages. A specialized 7B model outperforms a general 13B model for specific language pairs.
  6. Test with professional translators first — Have a native speaker review the first batch of translations. Use their feedback to refine your prompts and glossary. The initial calibration investment pays off across thousands of future translations.

Troubleshooting

Translations sound unnatural or robotic

Model outputs explanations instead of just the translation

Glossary terms are being ignored

Ollama connection issues from n8n Docker

Need More AI Workflow Templates?

Check out our full collection of 11 self-hosted AI workflows for n8n + Ollama. Translation, email automation, document processing, content generation, lead scoring, and more — all running locally on your hardware.

Get the Full Pack — $39

Related Tutorials