This workflow follows the Chainllm → Execute Workflow Trigger recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"name": "SRT Translator",
"nodes": [
{
"parameters": {
"formTitle": "SRT Translator",
"formDescription": "Translate SRT subtitle files to any language",
"formFields": {
"values": [
{
"fieldLabel": "srt_file",
"fieldType": "file",
"multipleFiles": false,
"acceptFileTypes": ".srt",
"requiredField": true
},
{
"fieldLabel": "target_lang",
"placeholder": "Spanish",
"requiredField": true
},
{
"fieldLabel": "source_lang",
"placeholder": "English (default)"
}
]
},
"responseMode": "lastNode",
"options": {}
},
"type": "n8n-nodes-base.formTrigger",
"typeVersion": 2.2,
"position": [
-6224,
3248
],
"id": "bb55a87c-b8d7-43f0-aa1e-23eca9a371a1",
"name": "On form submission"
},
{
"parameters": {
"jsCode": "/**\n * N8N Code Box: Subtitle Array Batch Splitter\n * Purpose: Splits subtitle arrays into smaller batches of N items for processing\n * Input: Items with 'subtitles' array property\n * Output: Multiple items, each containing a batch of subtitles with metadata\n */\nconst BATCH_SIZE = $input.item.json.BATCH_SIZE || 50;\nconst outputItems = [];\n\nfor (const item of $input.all()) {\n const subtitles = item.json.subtitles || [];\n\n if (subtitles.length === 0) {\n const { subtitles: _, ...restJson } = item.json;\n outputItems.push({\n json: {\n ...restJson,\n batch_subtitles: [],\n batch_number: 0,\n batch_size: 0,\n total_subtitles: 0,\n total_batches: 0\n }\n });\n continue;\n }\n\n const totalBatches = Math.ceil(subtitles.length / BATCH_SIZE);\n const { subtitles: _, ...restJson } = item.json;\n\n for (let i = 0; i < subtitles.length; i += BATCH_SIZE) {\n const batchNumber = Math.floor(i / BATCH_SIZE) + 1;\n const batch = subtitles.slice(i, i + BATCH_SIZE);\n\n outputItems.push({\n json: {\n ...restJson,\n batch_subtitles: batch,\n batch_number: batchNumber,\n batch_size: batch.length,\n total_subtitles: subtitles.length,\n total_batches: totalBatches,\n batch_start_index: i + 1,\n batch_end_index: Math.min(i + BATCH_SIZE, subtitles.length)\n }\n });\n }\n}\n\nreturn outputItems;\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-5344,
3008
],
"id": "6d6bc8cf-2a59-4cdb-a331-675eab6a4917",
"name": "Split in batches"
},
{
"parameters": {
"jsCode": "/**\n * N8N Code Box: Translation Batch Merger and SRT Data Combiner\n * Purpose: Collects all translated batches from input, deduplicates by index,\n * and combines in a single list\n * Input: All translated batch items\n * Output: Single item with all translations sorted by index\n */\nconst allTranslations = [];\n\nfor (const item of $input.all()) {\n const output = item.json.output || [];\n for (const t of output) {\n allTranslations.push(t);\n }\n}\n\n// Deduplicate by index, last write wins (handles retries), then sort ascending\nconst map = {};\nfor (const t of allTranslations) map[t.index] = t;\nconst sorted = Object.values(map).sort((a, b) => a.index - b.index);\n\nreturn [{\n json: {\n subtitles_translated: sorted,\n total_translations: sorted.length\n }\n}];\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-4832,
2992
],
"id": "ea1c7c47-fddc-423d-8580-ed7138f0cfb3",
"name": "Join batches"
},
{
"parameters": {
"content": "## Translate SRT\nTranslate the SRT lines in batches",
"height": 496,
"width": 868,
"color": 4
},
"type": "n8n-nodes-base.stickyNote",
"position": [
-5376,
2880
],
"typeVersion": 1,
"id": "e02a2753-ad7d-412a-9db9-a50a2fec103a",
"name": "Sticky Note"
},
{
"parameters": {
"content": "## Parse SRT\nReturns SRT contents as a JSON array",
"height": 240,
"width": 384,
"color": 5
},
"type": "n8n-nodes-base.stickyNote",
"position": [
-5824,
2864
],
"typeVersion": 1,
"id": "fee91d46-95f7-4117-8125-ae2463476f47",
"name": "Sticky Note1"
},
{
"parameters": {
"jsCode": "/**\n * N8N Code Box: SRT Subtitle Parser to JSON\n * Purpose: Parses SRT subtitle content and converts it to structured JSON format\n * Input: Items with 'file_content' property containing SRT formatted text\n * Output: Items with parsed subtitles array and count added to JSON\n * Throws: Error if no subtitles are found in the content\n */\n\n/**\n * Sanitizes subtitle text by replacing problematic characters\n * that can break JSON parsing or downstream processing\n */\nfunction sanitizeText(text) {\n return text\n // Curly/smart quotes \u2192 straight quotes\n .replace(/[\\u201C\\u201D\\u201E\\u201F\\u275D\\u275E]/g, '\"') // \" \" \u201e \u201f \u275d \u275e \u2192 \"\n .replace(/[\\u2018\\u2019\\u201A\\u201B\\u275B\\u275C]/g, \"'\") // ' ' \u201a \u201b \u275b \u275c \u2192 '\n // Dashes: em/en dash \u2192 hyphen\n .replace(/[\\u2013\\u2014]/g, '-')\n // Ellipsis character \u2192 three dots\n .replace(/\\u2026/g, '...')\n // Non-breaking space \u2192 regular space\n .replace(/\\u00A0/g, ' ')\n // Zero-width characters\n .replace(/[\\u200B\\u200C\\u200D\\uFEFF]/g, '')\n // Trim stray whitespace\n .trim();\n}\n\nfor (const item of $input.all()) {\n // Get SRT content and clean it\n let srtContent = item.json.file_content || '';\n \n // Remove BOM if present\n srtContent = srtContent.replace(/^\\uFEFF/, '');\n \n // Remove carriage returns and normalize line breaks\n srtContent = srtContent.replace(/\\r\\n/g, '\\n').replace(/\\r/g, '\\n');\n \n const subtitlesList = [];\n \n if (srtContent) {\n // Split into blocks by double line break\n const blocks = srtContent.split(/\\n\\s*\\n+/);\n \n for (const block of blocks) {\n if (!block.trim()) continue;\n \n // Split into lines and clean\n const lines = block.trim().split('\\n').map(line => line.trim()).filter(line => line);\n \n // Verify we have at least 3 lines (index, timestamp, text)\n if (lines.length < 3) continue;\n \n // Extract index\n const index = parseInt(lines[0]);\n if (isNaN(index)) continue;\n \n // Extract timestamps\n const timestampLine = lines[1];\n if (!timestampLine.includes('-->')) continue;\n \n const timeParts = timestampLine.split('-->');\n if (timeParts.length !== 2) continue;\n \n const startTime = timeParts[0].trim();\n const endTime = timeParts[1].trim();\n \n // Extract text (can be multiple lines) and sanitize\n const text = sanitizeText(lines.slice(2).join(' '));\n \n // Create subtitle object\n const subtitle = {\n index: index,\n start: startTime,\n end: endTime,\n text: text\n };\n \n subtitlesList.push(subtitle);\n }\n }\n \n // Throw error if no subtitles were parsed\n if (subtitlesList.length === 0) {\n throw new Error('No subtitles found in SRT content');\n }\n \n // Add parsed data to item's JSON\n item.json.subtitles = subtitlesList;\n item.json.subtitles_count = subtitlesList.length;\n \n // Remove file_content to free up memory\n delete item.json.file_content;\n}\nreturn $input.all();"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-5568,
2944
],
"id": "a7e7a690-6643-4bfa-a336-524b03892b06",
"name": "Parse SRT",
"onError": "continueErrorOutput"
},
{
"parameters": {
"content": "## Generate SRT\nRetrieve the translated lines and creates a new SRT file",
"height": 240,
"width": 400,
"color": 5
},
"type": "n8n-nodes-base.stickyNote",
"position": [
-4464,
2848
],
"typeVersion": 1,
"id": "d2215c6f-16c4-4630-ad7e-1ccae494b91d",
"name": "Sticky Note2"
},
{
"parameters": {
"jsCode": "/**\n * N8N Code Box: Original and Translated Subtitle Merger\n * Purpose: Merges original subtitles with translations based on index, preserving timing data\n * Input: Single item with 'subtitles' (original) and 'subtitles_translated' arrays\n * Output: Items with merged subtitles using original timing and translated text\n */\n\nconst results = [];\n\nfor (const item of $input.all()) {\n const originalSubtitles = item.json.subtitles || [];\n const translatedSubtitles = item.json.subtitles_translated || [];\n\n const translationsMap = {};\n translatedSubtitles.forEach(t => {\n translationsMap[t.index] = t.text;\n });\n\n const mergedSubtitles = originalSubtitles.map(subtitle => ({\n index: subtitle.index,\n start: subtitle.start,\n end: subtitle.end,\n text: translationsMap[subtitle.index] || subtitle.text\n }));\n\n results.push({\n json: {\n merged_subtitles: mergedSubtitles,\n target_lang: item.json.target_lang,\n srt_file: item.json.srt_file,\n total_subtitles: mergedSubtitles.length\n }\n });\n}\n\nreturn results;\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-4416,
2928
],
"id": "62730897-10c4-4530-a6c5-32c994210882",
"name": "Merge Original and Translated Subtitles"
},
{
"parameters": {
"jsCode": "/**\n * N8N Code Box: SRT File Generator from Merged Subtitles\n * Purpose: Converts merged subtitle data back to SRT format and creates binary file\n * Input: Items with 'merged_subtitles' array containing subtitle objects\n * Output: Items with SRT content as text and binary file, plus metadata (lines, size)\n */\n\n// Generate SRT file from merged subtitles\nfor (const item of $input.all()) {\n const mergedSubtitles = item.json.merged_subtitles || [];\n \n if (mergedSubtitles.length === 0) {\n item.json.srt_content = '';\n item.json.srt_lines = 0;\n // Remove merged_subtitles to free up memory\n delete item.json.merged_subtitles;\n continue;\n }\n \n // Sort by index to ensure correct order\n const sortedSubtitles = mergedSubtitles.sort((a, b) => a.index - b.index);\n \n // Generate SRT content\n const srtBlocks = [];\n \n for (const subtitle of sortedSubtitles) {\n // SRT format:\n // Subtitle number\n // start_timestamp --> end_timestamp\n // Subtitle text\n // Empty line\n \n const srtBlock = `${subtitle.index}\n${subtitle.start} --> ${subtitle.end}\n${subtitle.text}`;\n \n srtBlocks.push(srtBlock);\n }\n \n // Join blocks with double line break\n const srtContent = srtBlocks.join('\\n\\n');\n \n // Add SRT content to item\n // item.json.srt_content = srtContent;\n item.json.srt_lines = srtBlocks.length;\n item.json.srt_size = srtContent.length;\n \n // Create binary SRT file\n const originalFileName = item.json.srt_file.file_name || 'subtitles.srt';\n const targetLang = item.json.target_lang || 'translated';\n \n // Generate new filename: originalname.TARGET_LANG.srt\n const fileNameWithoutExt = originalFileName.replace(/\\.srt$/i, '');\n const newFileName = `${fileNameWithoutExt}.${targetLang}.srt`;\n \n // Convert to Base64 for binary storage\n const buffer = Buffer.from(srtContent, 'utf-8');\n const base64Content = buffer.toString('base64');\n \n // Add as binary file\n item.binary = item.binary || {};\n item.binary['translated_srt'] = {\n data: base64Content,\n fileName: newFileName,\n mimeType: 'text/plain'\n };\n \n // Remove merged_subtitles to free up memory\n delete item.json.merged_subtitles;\n}\nreturn $input.all();"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-4192,
2928
],
"id": "b418dd80-8342-41a7-a136-d553feaed2f8",
"name": "Generate SRT file"
},
{
"parameters": {
"workflowInputs": {
"values": [
{
"name": "srt_file",
"type": "object"
},
{
"name": "target_lang"
},
{
"name": "source_lang"
},
{
"name": "binary_file_name"
}
]
}
},
"type": "n8n-nodes-base.executeWorkflowTrigger",
"typeVersion": 1.1,
"position": [
-6224,
2944
],
"id": "30a5c581-8caf-4a12-8fea-b82a4eab6389",
"name": "When Executed by Another Workflow"
},
{
"parameters": {
"workflowId": {
"__rl": true,
"value": "={{ $workflow.id }}",
"mode": "id",
"cachedResultUrl": "/workflow/=%7B%7B%20$workflow.id%20%7D%7D"
},
"workflowInputs": {
"mappingMode": "defineBelow",
"value": {
"srt_file": "={{ $json.srt_file }}",
"target_lang": "={{ $json.target_lang }}",
"source_lang": "={{ $json.source_lang }}",
"binary_file_name": "srt_file"
},
"matchingColumns": [],
"schema": [
{
"id": "srt_file",
"displayName": "srt_file",
"required": false,
"defaultMatch": false,
"display": true,
"canBeUsedToMatch": true,
"type": "object",
"removed": false
},
{
"id": "target_lang",
"displayName": "target_lang",
"required": false,
"defaultMatch": false,
"display": true,
"canBeUsedToMatch": true,
"type": "string",
"removed": false
},
{
"id": "source_lang",
"displayName": "source_lang",
"required": false,
"defaultMatch": false,
"display": true,
"canBeUsedToMatch": true,
"type": "string",
"removed": false
},
{
"id": "binary_file_name",
"displayName": "binary_file_name",
"required": false,
"defaultMatch": false,
"display": true,
"canBeUsedToMatch": true,
"type": "string",
"removed": false
}
],
"attemptToConvertTypes": false,
"convertFieldsToString": true
},
"mode": "each",
"options": {
"waitForSubWorkflow": true
}
},
"type": "n8n-nodes-base.executeWorkflow",
"typeVersion": 1.2,
"position": [
-6000,
3248
],
"id": "90419f6b-f9fe-43f8-ba0b-0a222ee5d8de",
"name": "Execute SRT Translate",
"onError": "continueErrorOutput"
},
{
"parameters": {
"operation": "completion",
"respondWith": "returnBinary",
"completionTitle": "Translation Complete \u2705",
"completionMessage": "Your subtitle file has been successfully translated and is ready for download.",
"inputDataFieldName": "translated_srt",
"limitWaitTime": true,
"resumeAmount": 5,
"resumeUnit": "minutes",
"options": {}
},
"type": "n8n-nodes-base.form",
"typeVersion": 1,
"position": [
-5776,
3152
],
"id": "958e365a-2ef5-4856-a39c-64e5c511c50c",
"name": "Form OK"
},
{
"parameters": {
"operation": "completion",
"completionTitle": "Translation Failed \u274c",
"completionMessage": "There was an error processing your subtitle file. Please try again",
"options": {}
},
"type": "n8n-nodes-base.form",
"typeVersion": 1,
"position": [
-5776,
3344
],
"id": "b50c953b-3950-4eb5-aaee-ff0beab8c067",
"name": "Form ERROR"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "4ff759d9-1b87-4861-9dbd-e9dabd0358eb",
"leftValue": "={{ $json.total_subtitles }}",
"rightValue": 0,
"operator": {
"type": "number",
"operation": "gt"
}
},
{
"id": "63467ef4-24f3-4129-a5ad-cf08214527cd",
"leftValue": "={{ $json.srt_lines }}",
"rightValue": "={{ $json.total_subtitles }}",
"operator": {
"type": "number",
"operation": "equals"
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
-4416,
3248
],
"id": "d959b077-4d57-4307-965a-cb8ea81bade3",
"name": "Translation OK?"
},
{
"parameters": {
"content": "### Return result\nOK - with file\nERROR - if something failed\n",
"height": 336,
"width": 384,
"color": 2
},
"type": "n8n-nodes-base.stickyNote",
"position": [
-4448,
3136
],
"typeVersion": 1,
"id": "1b385e4f-1bde-4fab-86ba-316af82d8d7e",
"name": "Sticky Note3"
},
{
"parameters": {
"updates": [
"message"
],
"additionalFields": {
"download": true,
"userIds": "1613101109"
}
},
"type": "n8n-nodes-base.telegramTrigger",
"typeVersion": 1.2,
"position": [
-6224,
3696
],
"id": "d6c61452-b415-48d9-b922-b40ca278b24c",
"name": "Telegram Trigger",
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"chatId": "={{ $('Telegram Trigger').item.json.message.chat.id }}",
"text": "=Oops \ud83d\ude05 Something went wrong.\nPlease try again with your .SRT file and target language.\n",
"additionalFields": {
"appendAttribution": false,
"reply_to_message_id": "={{ $('Telegram Trigger').item.json.message.message_id }}"
}
},
"type": "n8n-nodes-base.telegram",
"typeVersion": 1.2,
"position": [
-4752,
3824
],
"id": "a45c9fd5-ff12-4186-9cd8-4b0516a2f1ce",
"name": "Reply with error",
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"rules": {
"values": [
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"leftValue": "={{ $json.message.document }}",
"rightValue": "",
"operator": {
"type": "object",
"operation": "notExists",
"singleValue": true
},
"id": "f207c61b-d43f-41ae-8ff4-756809a9f96f"
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "NO File"
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "d19f0484-1c91-4d19-84b1-c79bc21c29bd",
"leftValue": "={{ $json.message.document.file_name.toLowerCase() }}",
"rightValue": "srt",
"operator": {
"type": "string",
"operation": "notEndsWith"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "File not a SRT"
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "ee6b6cf3-5e43-4b83-9b06-e3e858e840bf",
"leftValue": "={{ $json.message.caption }}",
"rightValue": "",
"operator": {
"type": "string",
"operation": "empty",
"singleValue": true
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "No target lang"
}
]
},
"options": {
"fallbackOutput": "extra"
}
},
"type": "n8n-nodes-base.switch",
"typeVersion": 3.2,
"position": [
-6000,
3664
],
"id": "8b74a7a0-3cb2-4f99-8374-486f5d38ed31",
"name": "Check Message File"
},
{
"parameters": {
"operation": "sendDocument",
"chatId": "={{ $('Telegram Trigger').item.json.message.chat.id }}",
"binaryData": true,
"binaryPropertyName": "translated_srt",
"additionalFields": {
"caption": "=File translated to {{ $json.target_lang }} \u2705",
"reply_to_message_id": "={{ $('Telegram Trigger').item.json.message.message_id }}"
}
},
"type": "n8n-nodes-base.telegram",
"typeVersion": 1.2,
"position": [
-4976,
3712
],
"id": "cc176474-6e22-4024-8470-ee77afa61406",
"name": "Send Translated SRT",
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
},
"onError": "continueErrorOutput"
},
{
"parameters": {
"text": "={{ $json.message.caption }}",
"attributes": {
"attributes": [
{
"name": "target_lang",
"description": "Target Language",
"required": true
},
{
"name": "source_lang",
"description": "Source Language"
}
]
},
"options": {
"systemPromptTemplate": "=# Caption Language Extractor\n\n## Role\nYou are a language extraction parser. Extract translation intent from a Telegram caption and return a JSON object.\n\n## Extraction Rules\n- **target_lang** (mandatory): language to translate INTO. Normalize to full English name. If not found, return `\"unknown\"`.\n- **source_lang** (optional): language of the original subtitles, only if explicitly mentioned. If absent, return `\"\"`.\n\n## Output Format\nReturn ONLY valid JSON. No text outside it.\n```json\n{\"target_lang\": \"string\", \"source_lang\": \"string\"}\n```\n\n## Examples\n| Caption | target_lang | source_lang |\n|---|---|---|\n| `ES` | `\"Spanish\"` | `\"\"` |\n| `traduce a espa\u00f1ol` | `\"Spanish\"` | `\"\"` |\n| `from English to Spanish` | `\"Spanish\"` | `\"English\"` |\n| `traduce de ingl\u00e9s a espa\u00f1ol` | `\"Spanish\"` | `\"English\"` |"
}
},
"type": "@n8n/n8n-nodes-langchain.informationExtractor",
"typeVersion": 1.2,
"position": [
-5776,
3696
],
"id": "319a11e9-abee-4e02-8ffc-d4cda36c8a79",
"name": "Extract Source & Target Lang",
"alwaysOutputData": false
},
{
"parameters": {
"chatId": "={{ $('Telegram Trigger').item.json.message.chat.id }}",
"text": "=File received. Translating to '{{ $json.output.target_lang }}'... \u23f3",
"additionalFields": {
"appendAttribution": false,
"disable_notification": true,
"reply_to_message_id": "={{ $('Telegram Trigger').item.json.message.message_id }}"
}
},
"type": "n8n-nodes-base.telegram",
"typeVersion": 1.2,
"position": [
-5424,
3632
],
"id": "32621e68-9325-4597-b5bc-042a0c4791f1",
"name": "Send 'In Progress' message",
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"schemaType": "manual",
"inputSchema": "{\n \"json_schema\": {\n \"name\": \"translation_batch\",\n \"schema\": {\n \"type\": \"object\",\n \"properties\": {\n \"output\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"index\": {\n \"type\": \"integer\"\n },\n \"text\": {\n \"type\": \"string\"\n }\n },\n \"required\": [\n \"index\",\n \"text\"\n ]\n }\n }\n },\n \"required\": [\n \"output\"\n ]\n }\n }\n}"
},
"type": "@n8n/n8n-nodes-langchain.outputParserStructured",
"typeVersion": 1.3,
"position": [
-4960,
3200
],
"id": "7212ca16-59ae-4e8e-9517-bfc6102f4e43",
"name": "SRT indexed array format",
"onError": "continueRegularOutput",
"notes": "Sample:\n\n[\n {\n \"index\": 1,\n \"text\": \"Este es un archivo SRT de ejemplo.\"\n },\n {\n \"index\": 2,\n \"text\": \"Este es un archivo SRT de ejemplo.\"\n }\n]"
},
{
"parameters": {},
"type": "n8n-nodes-base.noOp",
"typeVersion": 1,
"position": [
-4192,
3152
],
"id": "ebf9aca6-88c3-4e39-b52f-4b962c50fdf1",
"name": "Return OK"
},
{
"parameters": {
"errorMessage": " There was an error processing the subtitle file"
},
"type": "n8n-nodes-base.stopAndError",
"typeVersion": 1,
"position": [
-4192,
3344
],
"id": "c745d72a-8267-478e-aa7d-b453e1d0f33b",
"name": "Return ERROR"
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "10c0668b-1098-479f-806c-dcca01dc9f54",
"name": "BATCH_SIZE",
"value": 150,
"type": "number"
},
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "LLM_PARALLEL_COUNT",
"value": 10,
"type": "number"
}
]
},
"includeOtherFields": true,
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
-6016,
2944
],
"id": "c2c911de-3187-4fb0-8673-be7daa61d335",
"name": "CONFIG"
},
{
"parameters": {
"operation": "text",
"binaryPropertyName": "={{ $json.binary_file_name }}",
"destinationKey": "file_content",
"options": {
"encoding": "utf8",
"stripBOM": true,
"keepSource": "json"
}
},
"type": "n8n-nodes-base.extractFromFile",
"typeVersion": 1.1,
"position": [
-5792,
2944
],
"id": "eb29e1f8-2df8-43c1-baf3-ceb418bbaab1",
"name": "Read file contents",
"alwaysOutputData": true
},
{
"parameters": {
"model": "openai/gpt-4.1-nano",
"options": {
"responseFormat": "json_object"
}
},
"type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
"typeVersion": 1,
"position": [
-5296,
3216
],
"id": "6e530003-2fdc-42cc-b75b-1a84f717859b",
"name": "OpenRouter - GPT 4.1 Nano",
"credentials": {
"openRouterApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"model": "google/gemini-2.5-flash-lite",
"options": {
"responseFormat": "json_object"
}
},
"type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
"typeVersion": 1,
"position": [
-5168,
3248
],
"id": "d8c17d48-ea50-46a4-b215-ec2fc16a699f",
"name": "OpenRouter - Gemini 2.5 Flash Lite",
"credentials": {
"openRouterApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"model": "google/gemini-2.5-flash-lite",
"options": {
"responseFormat": "json_object"
}
},
"type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
"typeVersion": 1,
"position": [
-5776,
3904
],
"id": "5380a2af-4400-4e29-af68-156cdf484a18",
"name": "OpenRouter - Gemini 2.5 Flash Lite (target extractor)",
"credentials": {
"openRouterApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"workflowId": {
"__rl": true,
"mode": "id",
"value": "={{ $workflow.id }}",
"cachedResultName": "={{ $workflow.id }}"
},
"workflowInputs": {
"mappingMode": "defineBelow",
"value": {
"srt_file": "={{ $json.message.document }}",
"target_lang": "={{ $json.output.target_lang }}",
"source_lang": "={{ $json.output.source_lang || 'auto-infer the source language' }}",
"binary_file_name": "data"
},
"matchingColumns": [],
"schema": [
{
"id": "srt_file",
"displayName": "srt_file",
"required": false,
"defaultMatch": false,
"display": true,
"canBeUsedToMatch": true,
"type": "object"
},
{
"id": "target_lang",
"displayName": "target_lang",
"required": false,
"defaultMatch": false,
"display": true,
"canBeUsedToMatch": true,
"type": "string"
},
{
"id": "source_lang",
"displayName": "source_lang",
"required": false,
"defaultMatch": false,
"display": true,
"canBeUsedToMatch": true,
"type": "string"
},
{
"id": "binary_file_name",
"displayName": "binary_file_name",
"required": false,
"defaultMatch": false,
"display": true,
"canBeUsedToMatch": true,
"type": "string"
}
],
"attemptToConvertTypes": false,
"convertFieldsToString": true
},
"options": {
"waitForSubWorkflow": true
}
},
"type": "n8n-nodes-base.executeWorkflow",
"typeVersion": 1.3,
"position": [
-5200,
3808
],
"id": "19ba0f93-c088-435c-b9d9-bfa18832b275",
"name": "Execute SRT Translate workflow",
"alwaysOutputData": true,
"onError": "continueErrorOutput"
},
{
"parameters": {
"mode": "combine",
"combineBy": "combineByPosition",
"options": {}
},
"type": "n8n-nodes-base.merge",
"typeVersion": 3.2,
"position": [
-4640,
2944
],
"id": "dc167dbf-0ead-40fe-8ef7-60a7823df5b8",
"name": "Aggregate SRT lines"
},
{
"parameters": {
"mode": "combine",
"combineBy": "combineByPosition",
"options": {}
},
"type": "n8n-nodes-base.merge",
"typeVersion": 3.2,
"position": [
-5424,
3824
],
"id": "919ffbcf-6640-4ae7-8aca-a092148945f4",
"name": "Recover context"
},
{
"parameters": {
"promptType": "define",
"text": "=INPUT -> {{ JSON.stringify($json.batch_subtitles.map(sub => ({ index: sub.index, text: sub.text }))) }}\n\n___",
"hasOutputParser": true,
"needsFallback": true,
"messages": {
"messageValues": [
{
"message": "=# Professional Subtitle Translation System\n\n## Role\nYou are a professional audiovisual subtitle translator. Your sole task is to translate subtitle entries from '{{ $json.source_lang || 'english' }}' to '{{ $json.target_lang }}' with linguistic precision and narrative consistency.\n\n## Input Format\nA JSON array of subtitle entries, each containing:\n- `index`: sequential identifier (integer)\n- `text`: subtitle text to translate\n\n## Translation Rules\n\n### Rule 1 \u2014 Context-First Analysis\nBefore translating any entry, read the entire batch. Identify:\n- Character genders and relationships\n- Speaker identity and emotional tone\n- Technical terms, proper nouns, recurring vocabulary\n- Formality register (tuteo vs. usted, t\u00fa vs. vous, etc.)\n\nUse this context to resolve every ambiguity consistently across the batch.\n\n### Rule 2 \u2014 Language Handling\n- If a line is already in {{ $json.target_lang }}: return it unchanged\n- If a line is in a language other than {{ $json.source_lang || 'english' }} but identifiable: translate it to {{ $json.target_lang }}\n- If a line is in an unidentifiable language: return it unchanged\n- If a line is empty, whitespace-only, or contains only symbols/numbers: return it unchanged\n\n### Rule 3 \u2014 Translation Quality\n- PRESERVE: meaning, tone, style, punctuation, and capitalization patterns\n- PRESERVE: similar text length to respect subtitle timing constraints\n- ENSURE: natural, idiomatic flow in {{ $json.target_lang }}\n- APPLY: cultural adaptation only when literal translation would impede comprehension\n- MAINTAIN: all terminology and character name decisions consistently across the batch\n\n## Output Format\nReturn ONLY a valid JSON object. No explanations, no comments, no markdown, no text outside the JSON.\n```json\n{\n \"output\": [\n {\"index\": int, \"text\": \"string\"},\n ...\n ]\n}\n```\n\n### Constraints\n- MUST contain exactly the same number of entries as the input\n- MUST NOT add, remove, or reorder entries\n- MUST NOT include any content outside the JSON object\n\n## Example\n\n**Input:**\n```json\n[\n {\"index\": 47, \"text\": \"Maria walked into the room.\"},\n {\"index\": 48, \"text\": \"She looked tired.\"},\n {\"index\": 49, \"text\": \"Good morning, doctor.\"}\n]\n```\n\n**Output (English \u2192 Spanish):**\n```json\n{\n \"output\": [\n {\"index\": 47, \"text\": \"Mar\u00eda entr\u00f3 en la habitaci\u00f3n.\"},\n {\"index\": 48, \"text\": \"Se ve\u00eda cansada.\"},\n {\"index\": 49, \"text\": \"Buenos d\u00edas, doctora.\"}\n ]\n}\n```\n"
}
]
},
"batching": {
"batchSize": "={{ $('CONFIG').item.json.LLM_PARALLEL_COUNT }}"
}
},
"type": "@n8n/n8n-nodes-langchain.chainLlm",
"typeVersion": 1.7,
"position": [
-5152,
2992
],
"id": "664ef063-b24f-4b0d-adcd-6cd5011524cf",
"name": "Translate lines",
"retryOnFail": true,
"waitBetweenTries": 5000
},
{
"parameters": {
"chatId": "={{ $json.message.chat.id }}",
"text": "=Hi \ud83d\udc4b Please send me: \n- Your .SRT file \n- The target language (e.g. English, French...) in the file description ",
"additionalFields": {
"appendAttribution": false,
"disable_notification": true,
"reply_to_message_id": "={{ $json.message.message_id }}"
}
},
"type": "n8n-nodes-base.telegram",
"typeVersion": 1.2,
"position": [
-5712,
3536
],
"id": "bad9382c-e4fe-4a3e-ae47-c8b31258c77d",
"name": "Send instructions",
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
}
}
],
"connections": {
"On form submission": {
"main": [
[
{
"node": "Execute SRT Translate",
"type": "main",
"index": 0
}
]
]
},
"Split in batches": {
"main": [
[
{
"node": "Translate lines",
"type": "main",
"index": 0
}
]
]
},
"Join batches": {
"main": [
[
{
"node": "Aggregate SRT lines",
"type": "main",
"index": 1
}
]
]
},
"Parse SRT": {
"main": [
[
{
"node": "Split in batches",
"type": "main",
"index": 0
},
{
"node": "Aggregate SRT lines",
"type": "main",
"index": 0
}
],
[
{
"node": "Return ERROR",
"type": "main",
"index": 0
}
]
]
},
"Merge Original and Translated Subtitles": {
"main": [
[
{
"node": "Generate SRT file",
"type": "main",
"index": 0
}
]
]
},
"Generate SRT file": {
"main": [
[
{
"node": "Translation OK?",
"type": "main",
"index": 0
}
]
]
},
"When Executed by Another Workflow": {
"main": [
[
{
"node": "CONFIG",
"type": "main",
"index": 0
}
]
]
},
"Execute SRT Translate": {
"main": [
[
{
"node": "Form OK",
"type": "main",
"index": 0
}
],
[
{
"node": "Form ERROR",
"type": "main",
"index": 0
}
]
]
},
"Translation OK?": {
"main": [
[
{
"node": "Return OK",
"type": "main",
"index": 0
}
],
[
{
"node": "Return ERROR",
"type": "main",
"index": 0
}
]
]
},
"Telegram Trigger": {
"main": [
[
{
"node": "Check Message File",
"type": "main",
"index": 0
}
]
]
},
"Check Message File": {
"main": [
[
{
"node": "Send instructions",
"type": "main",
"index": 0
}
],
[
{
"node": "Send instructions",
"type": "main",
"index": 0
}
],
[
{
"node": "Send instructions",
"type": "main",
"index": 0
}
],
[
{
"node": "Extract Source & Target Lang",
"type": "main",
"index": 0
},
{
"node": "Recover context",
"type": "main",
"index": 1
}
]
]
},
"Extract Source & Target Lang": {
"main": [
[
{
"node": "Send 'In Progress' message",
"type": "main",
"index": 0
},
{
"node": "Recover context",
"type": "main",
"index": 0
}
]
]
},
"SRT indexed array format": {
"ai_outputParser": [
[
{
"node": "Translate lines",
"type": "ai_outputParser",
"index": 0
}
]
]
},
"CONFIG": {
"main": [
[
{
"node": "Read file contents",
"type": "main",
"index": 0
}
]
]
},
"Read file contents": {
"main": [
[
{
"node": "Parse SRT",
"type": "main",
"index": 0
}
]
]
},
"OpenRouter - GPT 4.1 Nano": {
"ai_languageModel": [
[
{
"node": "Translate lines",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"OpenRouter - Gemini 2.5 Flash Lite": {
"ai_languageModel": [
[
{
"node": "Translate lines",
"type": "ai_languageModel",
"index": 1
}
]
]
},
"OpenRouter - Gemini 2.5 Flash Lite (target extractor)": {
"ai_languageModel": [
[
{
"node": "Extract Source & Target Lang",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Execute SRT Translate workflow": {
"main": [
[
{
"node": "Send Translated SRT",
"type": "main",
"index": 0
}
],
[
{
"node": "Reply with error",
"type": "main",
"index": 0
}
]
]
},
"Aggregate SRT lines": {
"main": [
[
{
"node": "Merge Original and Translated Subtitles",
"type": "main",
"index": 0
}
]
]
},
"Send Translated SRT": {
"main": [
[],
[
{
"node": "Reply with error",
"type": "main",
"index": 0
}
]
]
},
"Recover context": {
"main": [
[
{
"node": "Execute SRT Translate workflow",
"type": "main",
"index": 0
}
]
]
},
"Translate lines": {
"main": [
[
{
"node": "Join batches",
"type": "main",
"index": 0
}
]
]
}
},
"active": true,
"settings": {
"executionOrder": "v1",
"binaryMode": "separate"
},
"versionId": "0665c796-7d0d-442e-814c-856acbf20117",
"id": "fdSpvUNx0vyf0kMJ",
"tags": [
{
"updatedAt": "2025-08-20T18:31:53.488Z",
"createdAt": "2025-08-20T18:31:53.488Z",
"id": "MYQWvz4yQohlX8Dp",
"name": "telegram"
},
{
"updatedAt": "2025-08-15T08:45:03.140Z",
"createdAt": "2025-08-15T08:45:03.140Z",
"id": "iiq7yealSFabpVUg",
"name": "ai"
},
{
"updatedAt": "2025-08-20T20:35:44.435Z",
"createdAt": "2025-08-20T20:35:44.435Z",
"id": "syw5irhKWlVWyLTC",
"name": "utils"
}
]
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
openRouterApitelegramApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
SRT Translator. Uses formTrigger, executeWorkflowTrigger, form, telegramTrigger. Event-driven trigger; 34 nodes.
Source: https://github.com/alejandrosnz/srt-llm-translator/blob/bdcf85fe153b2ea49abe83c4cce65b4a3a76ab1a/n8n/n8n_workflow.json — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
This comprehensive N8N automation template revolutionizes content creation by delivering a complete end-to-end solution for AI-powered blog generation. Transform simple ideas into fully SEO-optimized,
End-to-End Video Creation from user idea or transcript AI-Powered Scriptwriting using LLMs (e.g., DeepSeek via OpenRouter) Voiceover Generation with customizable TTS voices Image Scene Generation usin
Effortlessly track your expenses with MoneyMate, an n8n workflow that transforms receipts into organized financial insights.
Disclaimer: This template requires the community node, which is only available on self-hosted n8n instances. You’ll need a self-hosted n8n setup to use this workflow.
This workflow turns a short text idea into stunning, AI-generated anime-style images, all from Telegram. It combines a chat LLM for prompt enhancement with Gemini (free) or Leonardo.AI (paid) image mo