{
  "id": "jcqcULGGIS3CRxFD",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Slack Auto Translator (/trans) \u2014 JA \u21c4 EN",
  "tags": [],
  "nodes": [
    {
      "id": "27a5c8da-5f3a-42c4-a5b3-661ba6273ae2",
      "name": "Webhook (Slash Command)",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -48,
        -224
      ],
      "parameters": {
        "path": "slack/trans",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 2
    },
    {
      "id": "b1f97605-e600-4cb0-bfda-fa2d46f97b04",
      "name": "Respond to Webhook",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        400,
        -224
      ],
      "parameters": {
        "options": {
          "responseCode": 200,
          "responseHeaders": {
            "entries": [
              {
                "name": "Content-Type",
                "value": "text/plain; charset=utf-8"
              }
            ]
          }
        },
        "respondWith": "text",
        "responseBody": "={{$json[\"ack\"]}}"
      },
      "typeVersion": 1
    },
    {
      "id": "6a1b64d8-e2f2-480c-8587-85ae58be1fc6",
      "name": "Detect Language (Code)",
      "type": "n8n-nodes-base.code",
      "position": [
        160,
        16
      ],
      "parameters": {
        "jsCode": "// --- Parse Slack payload & trim ---\n// --- Slack\u306e\u30da\u30a4\u30ed\u30fc\u30c9\u3092\u53d6\u5f97\u3057\u3066\u6574\u5f62 ---\nconst body = $json.body || $json;\nconst raw = (body.text || '').trim();\n\n// --- Early return if empty ---\n// --- \u7a7a\u5165\u529b\u306f\u30a8\u30e9\u30fc\u30e1\u30c3\u30bb\u30fc\u30b8\u3092\u8fd4\u3057\u3066\u6253\u3061\u5207\u308a ---\nif (!raw) {\n  return [{ json: {\n    text: '',\n    target: '',\n    response_url: body.response_url,\n    user_id: body.user_id,\n    channel_id: body.channel_id,\n    error: 'Please provide text after /trans.' // \u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044\n  }}];\n}\n\n// --- Optional override: \"en: \u3053\u3093\u306b\u3061\u306f\" / \"ja: hello\" ---\n// --- \u660e\u793a\u30bf\u30fc\u30b2\u30c3\u30c8\u6307\u5b9a\u306b\u5bfe\u5fdc\uff08\u4f8b: en:, ja:\uff09---\nconst m = raw.match(/^(\\w{2}):\\s*(.*)$/);\nlet text = raw;\nlet override = null;\nif (m && (m[1] === 'en' || m[1] === 'ja')) {\n  override = m[1];\n  text = m[2];\n}\n\n// --- Heuristic JA detection ---\n// --- \u65e5\u672c\u8a9e\u30b9\u30af\u30ea\u30d7\u30c8\u5224\u5b9a ---\nconst hasJa = /[\\p{Script=Hiragana}\\p{Script=Katakana}\\p{Script=Han}]/u.test(text);\n\n// --- Decide target language (override wins) ---\n// --- \u30bf\u30fc\u30b2\u30c3\u30c8\u8a00\u8a9e\u6c7a\u5b9a\uff08\u660e\u793a\u6307\u5b9a\u3092\u512a\u5148\uff09---\nconst target = override ?? (hasJa ? 'en' : 'ja');\n\nreturn [{\n  json: {\n    text,\n    target,\n    response_url: body.response_url,\n    user_id: body.user_id,\n    channel_id: body.channel_id\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "48be3dda-347d-45b0-a663-2b9c5945e433",
      "name": "OpenAI (Chat) - Translate",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        512,
        16
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "id",
          "value": "gpt-4o-mini"
        },
        "options": {
          "temperature": 0.2
        },
        "messages": {
          "values": [
            {
              "role": "system",
              "content": "=You are a precise translator. Translate the input ONLY into the target language.\n- Preserve punctuation and line breaks.\n- Do NOT add explanations.\n- Keep style natural and concise.\n\nTarget language: {{$json[\"target\"]}}\n\nInput:\n{{$json[\"text\"]}}"
            },
            {
              "content": "={{$json[\"text\"]}}"
            }
          ]
        }
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.8
    },
    {
      "id": "2f4bb84e-3109-4f91-ae22-124b83e73d0c",
      "name": "Prepare Response",
      "type": "n8n-nodes-base.code",
      "position": [
        688,
        240
      ],
      "parameters": {
        "jsCode": "const j = $json;\n\n// --- Retrieve translated text (fallback-safe) ---\n// --- \u7ffb\u8a33\u7d50\u679c\u3092\u5b89\u5168\u306b\u53d6\u5f97 ---\nconst translated =\n  j.message?.content ??\n  j.choices?.[0]?.message?.content ??\n  j.data?.[0]?.content?.[0]?.text?.value ??\n  j.response ??\n  'No content';\n\n// --- Read original & user id from Detect node ---\n// --- Detect\u30ce\u30fc\u30c9\u304b\u3089\u539f\u6587\u3068\u30e6\u30fc\u30b6\u30fcID\u3092\u53d6\u5f97 ---\nconst detect = $node[\"Detect Language (Code)\"].json;\nconst original = detect.text;\nconst userId = detect.user_id;\n\n// --- Public message format ---\n// --- \u516c\u958b\u30e1\u30c3\u30bb\u30fc\u30b8\u306e\u6574\u5f62 ---\nconst reply = `<@${userId}>\\n> ${original}\\n${translated}`;\n// const reply = `<@${userId}> ${translated}`; // \u2190 \u7ffb\u8a33\u3060\u3051\u6d3e\u306b\u3057\u305f\u3044\u6642\u306f\u3053\u306e\u884c\u306b\u5207\u66ff\n\nreturn [{ json: { reply, response_url: j.response_url } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "cdeb82fe-2702-4fbf-a2c4-3f6263c289aa",
      "name": "Code (Ack)",
      "type": "n8n-nodes-base.code",
      "position": [
        160,
        -224
      ],
      "parameters": {
        "jsCode": "return [{ json: { ack: \"Translating...\" } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "24c37c6e-7d41-4828-b284-aafe250b16d8",
      "name": "HTTP Request",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        864,
        240
      ],
      "parameters": {
        "url": "={{$json[\"response_url\"]}}",
        "method": "POST",
        "options": {},
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "response_type",
              "value": "in_channel"
            },
            {
              "name": "text",
              "value": "={{$json[\"reply\"]}}"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "ca69bd68-8977-4a28-8a72-c889af35b1d2",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        864,
        144
      ],
      "parameters": {
        "color": 2,
        "height": 96,
        "content": "Posts the final translation back to Slack (in_channel by default).\nSwitch to ephemeral if you prefer private replies."
      },
      "typeVersion": 1
    },
    {
      "id": "68b6b3b8-7883-4e9b-93f8-83bfc8a7e85b",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        160,
        -80
      ],
      "parameters": {
        "color": 4,
        "height": 96,
        "content": "Detects JA/EN automatically. Supports overrides like \nen: \u3053\u3093\u306b\u3061\u306f \nand handles empty input."
      },
      "typeVersion": 1
    },
    {
      "id": "9d050866-b804-44ae-a81a-91b925ebc9fd",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        512,
        -64
      ],
      "parameters": {
        "color": 6,
        "width": 288,
        "height": 80,
        "content": "Uses gpt-4o-mini with temperature 0.2 for stable translations."
      },
      "typeVersion": 1
    },
    {
      "id": "ec9fb181-2acd-4d17-a367-b61b75c7e180",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        160,
        -304
      ],
      "parameters": {
        "color": 5,
        "width": 208,
        "height": 80,
        "content": "Sends an immediate \u201cTranslating...\u201d response to avoid Slack\u2019s 3s timeout."
      },
      "typeVersion": 1
    },
    {
      "id": "5a2c7e05-221c-433e-bdd8-8e9fcb9504ad",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -96,
        -304
      ],
      "parameters": {
        "width": 192,
        "height": 80,
        "content": "Receives /trans requests from Slack. Uses Production URL in Slack."
      },
      "typeVersion": 1
    },
    {
      "id": "553ffc33-92aa-4274-aa93-555e25ba42da",
      "name": "Merge (Combine Response)",
      "type": "n8n-nodes-base.merge",
      "position": [
        512,
        240
      ],
      "parameters": {
        "mode": "combine",
        "options": {},
        "combineBy": "combineByPosition"
      },
      "typeVersion": 3.2
    }
  ],
  "active": true,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "203ba874-0839-4b4b-bbab-61b1409ad662",
  "connections": {
    "Code (Ack)": {
      "main": [
        [
          {
            "node": "Respond to Webhook",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Response": {
      "main": [
        [
          {
            "node": "HTTP Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Respond to Webhook": {
      "main": [
        []
      ]
    },
    "Detect Language (Code)": {
      "main": [
        [
          {
            "node": "OpenAI (Chat) - Translate",
            "type": "main",
            "index": 0
          },
          {
            "node": "Merge (Combine Response)",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Webhook (Slash Command)": {
      "main": [
        [
          {
            "node": "Detect Language (Code)",
            "type": "main",
            "index": 0
          },
          {
            "node": "Code (Ack)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge (Combine Response)": {
      "main": [
        [
          {
            "node": "Prepare Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI (Chat) - Translate": {
      "main": [
        [
          {
            "node": "Merge (Combine Response)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}