{
  "id": "AvORV0uVnYSN0wtlioH9i",
  "name": "Translation & Localization with DeepL and GPT-4o-mini",
  "tags": [],
  "nodes": [
    {
      "id": "90f17861-8d01-4ad3-8039-b3663b3be4d9",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        0,
        0
      ],
      "parameters": {
        "width": 400,
        "height": 760,
        "content": "## Translation & Localization with DeepL and GPT-4o-mini\n\n### Who is this for?\nContent teams, marketing agencies, and businesses that need to translate and culturally adapt content for international audiences.\n\n### What this workflow does\n- Receives content via webhook and validates target languages\n- Splits content for parallel processing using Split Out node\n- Translates using DeepL neural machine translation\n- Reviews quality with AI Agent and scores each output\n- Applies cultural localization adjustments\n- Aggregates all translations and returns unified response\n\n### Setup\n1. Connect DeepL API credentials\n2. Configure OpenAI credentials for AI Agent\n3. Set up Google Sheets for logging (optional)\n4. Configure Gmail for notifications\n\n### Requirements\n- DeepL API account\n- OpenAI API key\n- Google Sheets (optional)\n- Gmail account"
      },
      "typeVersion": 1
    },
    {
      "id": "a79dd7d0-3557-42cd-886d-97ff9026d660",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        544,
        112
      ],
      "parameters": {
        "color": 7,
        "width": 280,
        "height": 128,
        "content": "### **Step 1: Content Intake**\nReceive translation request via webhook and validate target language codes."
      },
      "typeVersion": 1
    },
    {
      "id": "6cbf8440-42ef-4e9a-b60b-8dc08757a273",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1152,
        112
      ],
      "parameters": {
        "color": 7,
        "width": 320,
        "height": 128,
        "content": "### **Step 2: Parallel Translation**\nSplit Out creates parallel streams for each language. DeepL translates with glossary support."
      },
      "typeVersion": 1
    },
    {
      "id": "2be5ce49-2ba8-43c9-aa35-be93c8d01d26",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2064,
        96
      ],
      "parameters": {
        "color": 7,
        "width": 280,
        "height": 128,
        "content": "### **Step 3: Quality Review**\nAI Agent evaluates accuracy, fluency, and style. Flags items needing human review."
      },
      "typeVersion": 1
    },
    {
      "id": "ff572b40-5d5a-47a6-8471-0870e4321924",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2624,
        96
      ],
      "parameters": {
        "color": 7,
        "width": 348,
        "height": 128,
        "content": "### **Step 4: Localization & Output**\nApply cultural adaptations, aggregate results, log to Sheets, and send notification."
      },
      "typeVersion": 1
    },
    {
      "id": "7a7e9a20-f4e7-498e-8f22-f4f9a894fcfa",
      "name": "Translation Request",
      "type": "n8n-nodes-base.webhook",
      "position": [
        528,
        288
      ],
      "parameters": {
        "path": "translate",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 2
    },
    {
      "id": "832fc1f8-561c-4be5-9884-ab7b4febc4e8",
      "name": "Initialize Translation Job",
      "type": "n8n-nodes-base.set",
      "position": [
        752,
        288
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "1",
              "name": "jobId",
              "type": "string",
              "value": "={{ 'TRN-' + $now.format('yyyyMMddHHmmss') }}"
            },
            {
              "id": "2",
              "name": "sourceText",
              "type": "string",
              "value": "={{ $json.body.sourceText }}"
            },
            {
              "id": "3",
              "name": "targetLanguages",
              "type": "object",
              "value": "={{ $json.body.targetLanguages }}"
            },
            {
              "id": "4",
              "name": "contentType",
              "type": "string",
              "value": "={{ $json.body.contentType || 'general' }}"
            },
            {
              "id": "5",
              "name": "glossaryId",
              "type": "string",
              "value": "={{ $json.body.glossaryId || '' }}"
            },
            {
              "id": "6",
              "name": "createdAt",
              "type": "string",
              "value": "={{ $now.toISO() }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "d30e3d66-8e87-455b-aaac-263f7079e759",
      "name": "Detect Source Language",
      "type": "n8n-nodes-base.code",
      "position": [
        976,
        288
      ],
      "parameters": {
        "jsCode": "const input = $input.first().json;\nconst languages = input.targetLanguages || ['en'];\n\nconst validLanguages = ['en', 'ja', 'de', 'fr', 'es', 'zh', 'ko', 'pt', 'it', 'nl', 'pl', 'ru'];\nconst normalizedLangs = languages.filter(l => validLanguages.includes(l.toLowerCase()));\n\nif (normalizedLangs.length === 0) {\n  throw new Error('No valid target languages specified');\n}\n\nconst sourceText = input.sourceText || '';\nlet detectedSource = 'en';\nif (/[\\u3040-\\u30ff\\u4e00-\\u9fff]/.test(sourceText)) detectedSource = 'ja';\nelse if (/[\\u0400-\\u04ff]/.test(sourceText)) detectedSource = 'ru';\n\nreturn [{\n  json: {\n    ...input,\n    detectedSourceLang: detectedSource,\n    validatedTargetLangs: normalizedLangs,\n    languageCount: normalizedLangs.length\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "4c4ff15f-e87f-4f0e-a6b1-d7b9f91ad635",
      "name": "Split by Target Language",
      "type": "n8n-nodes-base.splitOut",
      "position": [
        1200,
        288
      ],
      "parameters": {
        "include": "allOtherFields",
        "options": {},
        "fieldToSplitOut": "validatedTargetLangs"
      },
      "typeVersion": 1
    },
    {
      "id": "e73c5ac3-6d99-46db-97e5-3b1d04447f7b",
      "name": "Prepare Translation Request",
      "type": "n8n-nodes-base.set",
      "position": [
        1424,
        288
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "1",
              "name": "currentTargetLang",
              "type": "string",
              "value": "={{ $json.validatedTargetLangs }}"
            }
          ]
        },
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "2cb0ee68-21ca-48cf-8b07-f7845794da92",
      "name": "DeepL Translate",
      "type": "n8n-nodes-base.deepL",
      "position": [
        1648,
        288
      ],
      "parameters": {
        "text": "={{ $json.sourceText }}",
        "translateTo": "={{ $json.currentTargetLang }}",
        "additionalFields": {}
      },
      "typeVersion": 1
    },
    {
      "id": "8b1f1c6f-e5e8-4950-9184-90c17717195f",
      "name": "Process Translation Result",
      "type": "n8n-nodes-base.code",
      "position": [
        1872,
        288
      ],
      "parameters": {
        "jsCode": "const input = $input.first().json;\nconst prevData = $('Prepare Translation Request').first().json;\n\nreturn [{\n  json: {\n    jobId: prevData.jobId,\n    sourceText: prevData.sourceText,\n    targetLang: prevData.currentTargetLang,\n    translatedText: input.translations?.[0]?.text || input.text || '',\n    detectedSourceLang: input.translations?.[0]?.detected_source_language || prevData.detectedSourceLang,\n    contentType: prevData.contentType,\n    glossaryId: prevData.glossaryId\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "c7053efc-8d4c-466b-987e-301fe2cf3bb0",
      "name": "AI Quality Review",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        2096,
        288
      ],
      "parameters": {
        "text": "=Review this translation for quality:\n\nSource ({{ $json.detectedSourceLang }}): {{ $json.sourceText }}\nTranslation ({{ $json.targetLang }}): {{ $json.translatedText }}\nContent Type: {{ $json.contentType }}\n\nEvaluate:\n1. Accuracy (1-10)\n2. Fluency (1-10)\n3. Style (1-10)\n\nReturn JSON: { score: number, issues: string[], suggestions: string[] }",
        "options": {
          "systemMessage": "You are a professional translator. Evaluate translations objectively. Output valid JSON only."
        },
        "promptType": "define"
      },
      "typeVersion": 1.7
    },
    {
      "id": "dbc30730-1d81-417c-9616-032c0ffd9c80",
      "name": "OpenAI Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        2176,
        512
      ],
      "parameters": {
        "model": "gpt-4o-mini",
        "options": {
          "temperature": 0.3
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "da662a3d-1541-407d-a4c1-22deb3c7a160",
      "name": "Parse Quality Score",
      "type": "n8n-nodes-base.code",
      "position": [
        2448,
        288
      ],
      "parameters": {
        "jsCode": "const input = $input.first().json;\nconst aiOutput = input.output || '{}';\nconst translationData = $('Process Translation Result').first().json;\n\nlet qualityData = { score: 7, issues: [], suggestions: [] };\ntry {\n  const jsonMatch = aiOutput.match(/\\{[\\s\\S]*\\}/);\n  if (jsonMatch) qualityData = JSON.parse(jsonMatch[0]);\n} catch (e) {}\n\nreturn [{\n  json: {\n    ...translationData,\n    qualityScore: qualityData.score || 7,\n    issues: qualityData.issues || [],\n    suggestions: qualityData.suggestions || [],\n    needsReview: (qualityData.score || 7) < 6\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "8419c74f-934c-47eb-ad2a-4203229bc536",
      "name": "Apply Localization",
      "type": "n8n-nodes-base.code",
      "position": [
        2672,
        288
      ],
      "parameters": {
        "jsCode": "const input = $input.first().json;\n\nconst localizationRules = {\n  'ja': { dateFormat: 'YYYY\u5e74MM\u6708DD\u65e5', currency: '\u00a5' },\n  'de': { dateFormat: 'DD.MM.YYYY', currency: '\u20ac' },\n  'fr': { dateFormat: 'DD/MM/YYYY', currency: '\u20ac' },\n  'es': { dateFormat: 'DD/MM/YYYY', currency: '\u20ac' },\n  'zh': { dateFormat: 'YYYY\u5e74MM\u6708DD\u65e5', currency: '\u00a5' }\n};\n\nconst rules = localizationRules[input.targetLang] || {};\n\nreturn [{\n  json: {\n    ...input,\n    localizationApplied: Object.keys(rules).length > 0,\n    localizedText: input.translatedText,\n    localeSettings: rules,\n    processedAt: new Date().toISOString()\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "ada3fc35-ef3a-498e-87b5-59844ad353e2",
      "name": "Aggregate All Translations",
      "type": "n8n-nodes-base.aggregate",
      "position": [
        2896,
        288
      ],
      "parameters": {
        "options": {},
        "aggregate": "aggregateAllItemData",
        "destinationFieldName": "translations"
      },
      "typeVersion": 1
    },
    {
      "id": "ed8f18b0-a537-40af-b6e8-1380bc590e51",
      "name": "Build Translation Summary",
      "type": "n8n-nodes-base.code",
      "position": [
        3120,
        288
      ],
      "parameters": {
        "jsCode": "const aggregated = $input.first().json;\nconst translations = aggregated.translations || [];\n\nconst jobId = translations[0]?.jobId || 'unknown';\nconst sourceText = translations[0]?.sourceText || '';\n\nconst summary = {\n  jobId: jobId,\n  sourceText: sourceText,\n  totalLanguages: translations.length,\n  averageQualityScore: translations.reduce((sum, t) => sum + (t.qualityScore || 0), 0) / translations.length,\n  needsReviewCount: translations.filter(t => t.needsReview).length,\n  results: translations.map(t => ({\n    language: t.targetLang,\n    text: t.localizedText,\n    score: t.qualityScore,\n    needsReview: t.needsReview\n  })),\n  completedAt: new Date().toISOString()\n};\n\nreturn [{ json: summary }];"
      },
      "typeVersion": 2
    },
    {
      "id": "f6d2b1cb-66a2-4031-a66d-97b60bc76dc8",
      "name": "Log to Google Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        3344,
        288
      ],
      "parameters": {
        "columns": {
          "value": {
            "Job ID": "={{ $json.jobId }}",
            "Completed": "={{ $json.completedAt }}",
            "Languages": "={{ $json.totalLanguages }}",
            "Avg Quality": "={{ $json.averageQualityScore.toFixed(1) }}",
            "Source Text": "={{ $json.sourceText.substring(0, 200) }}"
          },
          "mappingMode": "defineBelow"
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "Translations"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "c58c2d5a-c38d-4915-80eb-b16aeb404f93",
      "name": "Send Completion Email",
      "type": "n8n-nodes-base.gmail",
      "position": [
        3344,
        528
      ],
      "parameters": {
        "sendTo": "=team@company.com",
        "message": "=Translation Job Completed\n\nJob ID: {{ $json.jobId }}\nLanguages: {{ $json.totalLanguages }}\nAvg Quality: {{ $json.averageQualityScore.toFixed(1) }}/10\n\nResults:\n{{ $json.results.map(r => `- ${r.language}: ${r.score}/10`).join('\\n') }}",
        "options": {},
        "subject": "=Translation Complete: {{ $json.jobId }}"
      },
      "typeVersion": 2.1
    },
    {
      "id": "720c0c5e-8779-4b6c-b7bf-b38a74080094",
      "name": "Return Results",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        3568,
        288
      ],
      "parameters": {
        "options": {},
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ success: true, jobId: $json.jobId, totalLanguages: $json.totalLanguages, averageQuality: $json.averageQualityScore, results: $json.results }) }}"
      },
      "typeVersion": 1.1
    }
  ],
  "active": false,
  "settings": {
    "availableInMCP": false,
    "executionOrder": "v1"
  },
  "versionId": "a4175205-73ca-4783-84c1-701b9ca3a64a",
  "connections": {
    "DeepL Translate": {
      "main": [
        [
          {
            "node": "Process Translation Result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Quality Review": {
      "main": [
        [
          {
            "node": "Parse Quality Score",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "AI Quality Review",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Apply Localization": {
      "main": [
        [
          {
            "node": "Aggregate All Translations",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Quality Score": {
      "main": [
        [
          {
            "node": "Apply Localization",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Translation Request": {
      "main": [
        [
          {
            "node": "Initialize Translation Job",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Log to Google Sheets": {
      "main": [
        [
          {
            "node": "Return Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Detect Source Language": {
      "main": [
        [
          {
            "node": "Split by Target Language",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split by Target Language": {
      "main": [
        [
          {
            "node": "Prepare Translation Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Translation Summary": {
      "main": [
        [
          {
            "node": "Log to Google Sheets",
            "type": "main",
            "index": 0
          },
          {
            "node": "Send Completion Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Aggregate All Translations": {
      "main": [
        [
          {
            "node": "Build Translation Summary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Initialize Translation Job": {
      "main": [
        [
          {
            "node": "Detect Source Language",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process Translation Result": {
      "main": [
        [
          {
            "node": "AI Quality Review",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Translation Request": {
      "main": [
        [
          {
            "node": "DeepL Translate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}