This workflow follows the Form → Form 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 →
{
"id": "vssVsRO0FW6InbaY",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "Translate",
"tags": [],
"nodes": [
{
"id": "7e55613e-c304-47cb-a017-2d912014ea8e",
"name": "Split Out",
"type": "n8n-nodes-base.splitOut",
"position": [
1180,
140
],
"parameters": {
"options": {},
"fieldToSplitOut": "txt"
},
"typeVersion": 1
},
{
"id": "1ab3e545-e7a1-4b3d-a190-d38cb55ebf96",
"name": "Google Translate",
"type": "n8n-nodes-base.googleTranslate",
"position": [
1620,
140
],
"parameters": {
"text": "={{ JSON.stringify($json.parts.secondPart) }}",
"translateTo": "={{ $json.language }}"
},
"credentials": {
"googleTranslateOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 2
},
{
"id": "07de7be3-5477-4e6c-b709-f632a3d5f162",
"name": "Aggregate",
"type": "n8n-nodes-base.aggregate",
"position": [
520,
340
],
"parameters": {
"options": {},
"aggregate": "aggregateAllItemData"
},
"typeVersion": 1
},
{
"id": "cbe5892e-3661-42fb-a850-1e0448a53e0a",
"name": "Edit Fields",
"type": "n8n-nodes-base.set",
"position": [
960,
340
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "498c663a-f372-40fb-9ac9-79f7a60875cc",
"name": "complete_text",
"type": "string",
"value": "={{ $json.complete_text }}"
},
{
"id": "34f3bc06-151d-4819-b6b8-515cf9c05c60",
"name": "file",
"type": "object",
"value": "={{$('Receive SRT File to Translate').first().json}}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "a4e1cc2e-bd2f-4cf7-af03-73e43cda83d3",
"name": "Convert to File",
"type": "n8n-nodes-base.convertToFile",
"position": [
1400,
340
],
"parameters": {
"options": {
"fileName": "={{ $json['Upload SRT file'].filename.replaceAll('.srt',` ${$('Prep Parts for Translate').first().json.language}.srt`)}}",
"mimeType": "={{ $json['Upload SRT file'].mimetype }}"
},
"operation": "toBinary",
"sourceProperty": "=data",
"binaryPropertyName": "file"
},
"typeVersion": 1.1
},
{
"id": "380bc679-4e08-4d5d-a263-d3d873f4f38f",
"name": "Split SRT Lines",
"type": "n8n-nodes-base.code",
"position": [
960,
140
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "let text = $json.data\n\ndelete $json.base64\ndelete $json.binary\n\n\n// Split by single newlines\nconst lines = text.split('\\n')\n\n// Create an array to hold grouped subtitle entries\nlet subtitleGroups = []\nlet currentGroup = []\n\n// Process each line\nfor (let i = 0; i < lines.length; i++) {\n const line = lines[i].trim()\n \n // If line is empty and we have content in currentGroup, \n // it's the end of a subtitle entry\n if (line === '' && currentGroup.length > 0) {\n subtitleGroups.push(currentGroup.join('\\n'))\n currentGroup = []\n } \n // If line is not empty, add to current group\n else if (line !== '') {\n currentGroup.push(line)\n }\n}\n\n// Add the last group if it has content\nif (currentGroup.length > 0) {\n subtitleGroups.push(currentGroup.join('\\n'))\n}\n\n// Remove any quotes at the beginning and end of the first and last entries\nif (subtitleGroups.length > 0) {\n subtitleGroups[0] = subtitleGroups[0].replace(/^\"/, '')\n subtitleGroups[subtitleGroups.length - 1] = subtitleGroups[subtitleGroups.length - 1].replace(/\"$/, '')\n}\n\n// Store the result\n$input.item.json.txt = subtitleGroups\n\nreturn $input.item;"
},
"typeVersion": 2
},
{
"id": "08215886-05f6-4ecc-9c1f-55c0e4cb6194",
"name": "Generate Binary",
"type": "n8n-nodes-base.code",
"position": [
1180,
340
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "function encodeBase64(text) {\n try {\n // For browser environments\n if (typeof window !== 'undefined') {\n // First, create a UTF-8 encoded string\n const utf8String = encodeURIComponent(text)\n .replace(/%([0-9A-F]{2})/g, (_, hex) => {\n return String.fromCharCode(parseInt(hex, 16));\n });\n \n // Then encode to Base64\n return btoa(utf8String);\n } \n // For Node.js environments\n else if (typeof Buffer !== 'undefined') {\n return Buffer.from(text).toString('base64');\n }\n \n throw new Error('Environment not supported for Base64 encoding');\n } catch (error) {\n console.error('Error encoding to Base64:', error);\n return null;\n }\n}\n\nlet data = encodeBase64($json.complete_text);\n\nconsole.log(data)\n\nlet file = $json.file\n\nfile.data = data;\n\nlet paddingCount = 0;\nif (data.endsWith('==')) paddingCount = 2;\nelse if (data.endsWith('=')) paddingCount = 1;\n\n// Calculate the decoded size (in bytes)\nfile.size = Math.floor(data.length * 3 / 4) - paddingCount;\n\n\nreturn file"
},
"typeVersion": 2
},
{
"id": "299122c1-61d1-4ce4-81b9-ce15d22cd49c",
"name": "Prep Parts for Translate",
"type": "n8n-nodes-base.code",
"position": [
1400,
140
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "function splitBySecondNewline(text) {\n // Find the position of the first newline\n const firstNewlinePos = text.indexOf('\\n');\n \n if (firstNewlinePos === -1) {\n return { firstPart: text, secondPart: '' }; // No newlines found\n }\n \n // Find the position of the second newline\n const secondNewlinePos = text.indexOf('\\n', firstNewlinePos + 1);\n \n if (secondNewlinePos === -1) {\n return { firstPart: text, secondPart: '' }; // Only one newline found\n }\n \n // Split the string at the second newline\n const firstPart = text.substring(0, secondNewlinePos);\n const secondPart = text.substring(secondNewlinePos + 1);\n \n return { firstPart, secondPart };\n}\n\nlet lang = $('Receive SRT File to Translate').first().json['Translate to Language']\n\nreturn {\n parts: splitBySecondNewline($json.txt),\n language: lang\n}"
},
"typeVersion": 2
},
{
"id": "8a810ef3-febe-42f7-91c9-6c82dddcc93a",
"name": "Clean Translations & Group Titles",
"type": "n8n-nodes-base.code",
"position": [
300,
340
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "let translated = $json.translatedText.replaceAll(\"\\\\n\",\"\\n\").replaceAll('"',\"\").replaceAll(''',\"'\");\n\nfunction splitIntoTwoLines(text, maxLength = 40) {\n // If text already contains a newline or is short enough, return as is\n if (text.includes('\\n') || text.length <= maxLength) {\n return text;\n }\n \n // Find the last space before or at the maxLength\n let splitIndex = text.lastIndexOf(' ', maxLength);\n \n // If no space was found (rare case with very long words)\n if (splitIndex === -1) {\n splitIndex = maxLength; // Force split at maxLength\n }\n \n // Split the text and join with a newline\n const firstLine = text.substring(0, splitIndex);\n const secondLine = text.substring(splitIndex + 1); // +1 to skip the space\n \n return firstLine + '\\n' + secondLine;\n}\n\n// Add a new field called 'myNewField' to the JSON of the item\n$input.item.json.complete = `${$('Prep Parts for Translate').item.json.parts.firstPart}\\n` + splitIntoTwoLines(translated)\n\nreturn $input.item;"
},
"typeVersion": 2
},
{
"id": "15b2781c-4b6f-43e7-9ca9-6d6114e5fdab",
"name": "Join completed text with double new line",
"type": "n8n-nodes-base.code",
"position": [
740,
340
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "let texts = $json.data.map(item=>{\n return item.complete\n})\n\n\n$input.item.json.complete_text = texts.join('\\n\\n')\n\nreturn $input.item;"
},
"typeVersion": 2
},
{
"id": "c43efbb6-3fe8-4aa3-8d65-ed3064bcc948",
"name": "Respond with file",
"type": "n8n-nodes-base.form",
"position": [
1620,
340
],
"parameters": {
"options": {},
"operation": "completion",
"respondWith": "returnBinary",
"completionTitle": "Done",
"inputDataFieldName": "file"
},
"typeVersion": 1
},
{
"id": "13103a23-3b1a-46d1-9731-c281ff1cac06",
"name": "Receive SRT File to Translate",
"type": "n8n-nodes-base.formTrigger",
"position": [
300,
140
],
"parameters": {
"options": {
"appendAttribution": false
},
"formTitle": "upload srt",
"formFields": {
"values": [
{
"fieldType": "dropdown",
"fieldLabel": "Translate to Language",
"fieldOptions": {
"values": [
{
"option": "EN"
},
{
"option": "JP"
}
]
},
"requiredField": true
},
{
"fieldType": "file",
"fieldLabel": "Upload SRT file",
"multipleFiles": false,
"requiredField": true,
"acceptFileTypes": ".srt"
}
]
},
"responseMode": "lastNode"
},
"typeVersion": 2.2
},
{
"id": "7e0f06f4-1e9d-436f-9310-325214e74bb9",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
280,
-280
],
"parameters": {
"width": 760,
"height": 300,
"content": "## Required Credentials\nhttps://docs.n8n.io/integrations/builtin/credentials/google/\n\n## Selecting Language\nYou can update the form to include your preferred language code (that you are translating to), by updating the dropdown field with a new option. \nOr update the Google Translate node language option back to 'fixed' and select your desired language. This will ignore the form option, but is safe to do."
},
"typeVersion": 1
},
{
"id": "29f9621e-3756-48ee-b6f0-e26a9f7aa247",
"name": "Extract text from Binary File",
"type": "n8n-nodes-base.extractFromFile",
"position": [
740,
140
],
"parameters": {
"options": {},
"operation": "text",
"binaryPropertyName": "Upload_SRT_file"
},
"typeVersion": 1
},
{
"id": "0924754e-6d1f-4d82-bb58-f64ebeac7b05",
"name": "Expose Binary",
"type": "n8n-nodes-base.code",
"position": [
520,
140
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "// Add a new field called 'myNewField' to the JSON of the item\n$input.item.json.binary = $binary;\n\nreturn $input.item;"
},
"typeVersion": 2
}
],
"active": true,
"settings": {
"executionOrder": "v1"
},
"versionId": "824adb39-806e-4d28-8e41-efd9f2e179a8",
"connections": {
"Aggregate": {
"main": [
[
{
"node": "Join completed text with double new line",
"type": "main",
"index": 0
}
]
]
},
"Split Out": {
"main": [
[
{
"node": "Prep Parts for Translate",
"type": "main",
"index": 0
}
]
]
},
"Edit Fields": {
"main": [
[
{
"node": "Generate Binary",
"type": "main",
"index": 0
}
]
]
},
"Expose Binary": {
"main": [
[
{
"node": "Extract text from Binary File",
"type": "main",
"index": 0
}
]
]
},
"Convert to File": {
"main": [
[
{
"node": "Respond with file",
"type": "main",
"index": 0
}
]
]
},
"Generate Binary": {
"main": [
[
{
"node": "Convert to File",
"type": "main",
"index": 0
}
]
]
},
"Split SRT Lines": {
"main": [
[
{
"node": "Split Out",
"type": "main",
"index": 0
}
]
]
},
"Google Translate": {
"main": [
[
{
"node": "Clean Translations & Group Titles",
"type": "main",
"index": 0
}
]
]
},
"Prep Parts for Translate": {
"main": [
[
{
"node": "Google Translate",
"type": "main",
"index": 0
}
]
]
},
"Extract text from Binary File": {
"main": [
[
{
"node": "Split SRT Lines",
"type": "main",
"index": 0
}
]
]
},
"Receive SRT File to Translate": {
"main": [
[
{
"node": "Expose Binary",
"type": "main",
"index": 0
}
]
]
},
"Clean Translations & Group Titles": {
"main": [
[
{
"node": "Aggregate",
"type": "main",
"index": 0
}
]
]
},
"Join completed text with double new line": {
"main": [
[
{
"node": "Edit Fields",
"type": "main",
"index": 0
}
]
]
}
}
}
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.
googleTranslateOAuth2Api
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
How this works
Easily translate text files or subtitles into multiple languages without manual effort, saving hours on content localisation for global audiences. This workflow suits content creators, marketers, or educators handling multilingual materials, delivering accurate translations via Google Translate integration. The key step involves splitting the input text, translating segments individually, then aggregating and converting them back into a downloadable file format like SRT for seamless reuse.
Use this workflow for batch-translating documents or subtitle files triggered by form submissions, ideal when processing large volumes quickly. Avoid it for real-time chat translations or highly specialised jargon requiring human review, as automated tools may miss nuances. Common variations include adapting the code nodes to handle different file types, such as PDFs, or chaining it with email notifications for completed translations.
About this workflow
Translate. Uses splitOut, googleTranslate, convertToFile, form. Event-driven trigger; 15 nodes.
Source: https://github.com/Zie619/n8n-workflows — 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.
Workflow Importer. Uses extractFromFile, executeCommand, readWriteFile, httpRequest. Event-driven trigger; 58 nodes.
Retrieves workflows directly from an n8n instance using the n8n API Dynamically generates a form to select which workflows to import Supports both fixed instance configuration and dynamic source/targe
Splitout Limit. Uses httpRequest, splitOut, removeDuplicates, splitInBatches. Event-driven trigger; 40 nodes.
Splitout Code. Uses airtable, formTrigger, form, splitOut. Event-driven trigger; 34 nodes.
This n8n template showcases a cool feature of n8n Forms where the form itself can be defined dynamically using the form fields schema.