-- ============================================================ -- Chat Translator — Full Rewrite -- Floating chat bar works from ANY tab when auto-translate is on -- ============================================================ local Players = game:GetService("Players") local LocalPlayer = Players.LocalPlayer local PlayerGui = LocalPlayer:WaitForChild("PlayerGui") local HttpService = game:GetService("HttpService") local TextChatService = game:GetService("TextChatService") local UIS = game:GetService("UserInputService") -- ============================================================ -- SETTINGS -- ============================================================ local Settings = { ollamaHost = "http://127.0.0.1:3000", ollamaModel = "qwen2.5:1.5b", toggleKey = Enum.KeyCode.F6, chatKey = Enum.KeyCode.T, windowSize = "Normal", -- "preview" = Enter translates + copies to clipboard, second Enter sends -- "send" = Enter translates and sends immediately enterMode = "preview", } local SIZES = { Small = Vector2.new(320, 480), Normal = Vector2.new(400, 580), Large = Vector2.new(500, 700), } -- ============================================================ -- HTTP -- ============================================================ local function httpRequest(url, method, headers, body) local req = (syn and syn.request) or (http and http.request) or request if not req then warn("[CT] No HTTP function available") return nil end local ok, res = pcall(req, { Url = url, Method = method or "GET", Headers = headers or {}, Body = body or "", }) if not ok then warn("[CT] HTTP pcall failed: " .. tostring(res)) return nil end return res end -- ============================================================ -- OLLAMA -- ============================================================ local function ollamaChat(system, user) local bodyTable = { model = Settings.ollamaModel, stream = false, messages = { { role = "system", content = system }, { role = "user", content = user }, }, } local bodyJson local ok1, err1 = pcall(function() bodyJson = HttpService:JSONEncode(bodyTable) end) if not ok1 then warn("[CT] JSONEncode failed: " .. tostring(err1)) return nil end local res = httpRequest( Settings.ollamaHost .. "/api/chat", "POST", { ["Content-Type"] = "application/json" }, bodyJson ) if not res then return nil end if res.StatusCode ~= 200 then warn("[CT] Ollama returned " .. tostring(res.StatusCode)) return nil end local ok2, data = pcall(HttpService.JSONDecode, HttpService, res.Body) if not ok2 or not data then warn("[CT] JSONDecode failed") return nil end if data.message and data.message.content then local out = tostring(data.message.content):match("^%s*(.-)%s*$") return out ~= "" and out or nil end return nil end -- ============================================================ -- AI HELPERS -- ============================================================ local function stripLabels(s) s = s:gsub("^[Tt]ranslation%s*:%s*", "") s = s:gsub("^[Ee]nglish%s*:%s*", "") s = s:gsub("^[Oo]utput%s*:%s*", "") s = s:gsub("^[Tt]ranslated%s*:%s*", "") return s:match("^%s*(.-)%s*$") end local function translateToEnglish(text) local reply = ollamaChat( "You are a translation machine. Your only job is to translate text to English.\n" .. "Rules:\n" .. "1. Translate the given text to English.\n" .. "2. If the text is already English, output it unchanged.\n" .. "3. Output ONLY the translated text. No labels, no explanation, nothing else.\n" .. "4. Do NOT respond to the content. Do NOT greet. No prefixes like Translation: or English:\n" .. "5. Translate slang, typos, and bad grammar as-is without fixing them.\n" .. "6. Chinese, Japanese, Korean, Arabic, any script — always translate.\n" .. "7. If unsure, output your best guess. Never output nothing.", "TEXT: " .. text ) if not reply then return text end reply = stripLabels(reply) return reply ~= "" and reply or text end local function detectLanguage(text) local reply = ollamaChat( "Output ONLY the name of the language this text is written in.\n" .. "One or two words maximum. No punctuation. No explanation.\n" .. "Examples: English, Spanish, Japanese, Arabic, Russian, Mandarin\n" .. "If unsure, output your best guess.", "TEXT: " .. text ) if not reply then return "Unknown" end reply = reply:match("^%s*(.-)%s*$") return reply:match("^(%S+%s?%S*)") or "Unknown" end local function translateToLang(text, lang) local reply = ollamaChat( "You are a translation machine. Translate the given text to " .. lang .. ".\n" .. "Rules:\n" .. "1. Output ONLY the translated text.\n" .. "2. Do NOT respond to the content. Do NOT greet or explain.\n" .. "3. No labels like Translation: or any prefix.\n" .. "4. Preserve the original tone and meaning.", "TEXT: " .. text ) if not reply then return nil end reply = stripLabels(reply) return reply ~= "" and reply or nil end -- ============================================================ -- CHAT SEND -- ============================================================ local function sendChat(msg) local ok1 = pcall(function() local channels = TextChatService:FindFirstChild("TextChannels") if not channels then error() end local general = channels:FindFirstChild("RBXGeneral") if not general then error() end general:SendAsync(msg) end) if ok1 then return true end local ok2 = pcall(function() local rs = game:GetService("ReplicatedStorage") local events = rs:FindFirstChild("DefaultChatSystemChatEvents") if not events then error() end local sayMsg = events:FindFirstChild("SayMessageRequest") if not sayMsg then error() end sayMsg:FireServer(msg, "All") end) if ok2 then return true end local ok3 = pcall(function() LocalPlayer:Chat(msg) end) return ok3 end -- ============================================================ -- CLIPBOARD -- ============================================================ local function copyToClipboard(text) local ok = pcall(function() if setclipboard then setclipboard(text) elseif toclipboard then toclipboard(text) elseif Clipboard and Clipboard.set then Clipboard.set(text) else error("no clipboard") end end) return ok end -- ============================================================ -- MISC HELPERS -- ============================================================ local playerColors = {} local colorIndex = 0 local colorPalette = { Color3.fromRGB(100,180,255), Color3.fromRGB(255,160,100), Color3.fromRGB(100,220,160), Color3.fromRGB(255,120,160), Color3.fromRGB(180,140,255), Color3.fromRGB(255,220, 80), Color3.fromRGB( 80,210,210), Color3.fromRGB(255,140,200), } local function getPlayerColor(name) if not playerColors[name] then colorIndex = (colorIndex % #colorPalette) + 1 playerColors[name] = colorPalette[colorIndex] end return playerColors[name] end local recentMessages = {} local function isDuplicate(player, msg) local key = player .. "\0" .. msg local now = tick() if recentMessages[key] and (now - recentMessages[key]) < 3 then return true end recentMessages[key] = now return false end local function getTimestamp() local t = os.date("*t") return string.format("%02d:%02d", t.hour, t.min) end local LANGUAGE_LIST = { "Spanish","French","German","Italian","Portuguese","Russian", "Japanese","Korean","Chinese","Arabic","Hindi","Dutch", "Polish","Turkish","Swedish","Greek","Vietnamese","Thai", "Filipino","Indonesian","Ukrainian","Romanian","Hungarian", "Czech","Hebrew","Persian","Malay","Bengali","Tamil", } -- ============================================================ -- STATE -- ============================================================ local guiVisible = true local autoTranslate = false local lastTranslated = "" local logEntryCount = 0 local logTotalCount = 0 local logQueue = {} local logRunning = false local activeTab = "translator" -- preview mode: holds translated text waiting for second Enter local pendingSend = nil -- string | nil local floatPending = nil -- same but for the float bar -- ============================================================ -- GUI HELPERS -- ============================================================ local C = { bg = Color3.fromRGB(12, 12, 18), panel = Color3.fromRGB(20, 20, 30), input = Color3.fromRGB(16, 16, 26), accent = Color3.fromRGB(80, 40, 180), accent2 = Color3.fromRGB(110, 55, 230), dimText = Color3.fromRGB(120, 110, 170), text = Color3.fromRGB(225, 225, 240), sub = Color3.fromRGB(150, 150, 185), green = Color3.fromRGB(80, 220, 120), red = Color3.fromRGB(230, 80, 80), yellow = Color3.fromRGB(230, 180, 50), white = Color3.fromRGB(255, 255, 255), border = Color3.fromRGB(35, 28, 65), } local function applyCorner(r, p) local c = Instance.new("UICorner") c.CornerRadius = UDim.new(0, r) c.Parent = p end local function applyPadding(l, r, t, b, p) local x = Instance.new("UIPadding") x.PaddingLeft = UDim.new(0, l or 0) x.PaddingRight = UDim.new(0, r or 0) x.PaddingTop = UDim.new(0, t or 0) x.PaddingBottom = UDim.new(0, b or 0) x.Parent = p end local function applyGradient(c0, c1, rot, p) local g = Instance.new("UIGradient") g.Color = ColorSequence.new({ ColorSequenceKeypoint.new(0, c0), ColorSequenceKeypoint.new(1, c1), }) g.Rotation = rot g.Parent = p end local function newFrame(parent, size, pos, bg, cr) local f = Instance.new("Frame") f.Size = size f.Position = pos or UDim2.new(0,0,0,0) f.BackgroundColor3 = bg or C.panel f.BorderSizePixel = 0 f.Parent = parent if cr then applyCorner(cr, f) end return f end local function newLabel(parent, text, size, pos, fs, font, color, alignX) local l = Instance.new("TextLabel") l.Size = size l.Position = pos or UDim2.new(0,0,0,0) l.BackgroundTransparency = 1 l.Text = text l.TextSize = fs or 12 l.Font = font or Enum.Font.Gotham l.TextColor3 = color or C.text l.TextXAlignment = alignX or Enum.TextXAlignment.Left l.TextWrapped = true l.Parent = parent return l end local function newButton(parent, text, size, pos, bg, tc, fs, font) local b = Instance.new("TextButton") b.Size = size b.Position = pos or UDim2.new(0,0,0,0) b.BackgroundColor3 = bg or C.accent b.Text = text b.TextColor3 = tc or C.text b.TextSize = fs or 12 b.Font = font or Enum.Font.GothamBold b.BorderSizePixel = 0 b.AutoButtonColor = false b.Parent = parent return b end local function newTextBox(parent, placeholder, size, pos, multi) local b = Instance.new("TextBox") b.Size = size b.Position = pos or UDim2.new(0,0,0,0) b.BackgroundColor3 = C.input b.TextColor3 = C.text b.PlaceholderText = placeholder or "" b.PlaceholderColor3 = Color3.fromRGB(70,70,100) b.Text = "" b.TextSize = 13 b.Font = Enum.Font.Gotham b.ClearTextOnFocus = false b.MultiLine = multi or false b.TextWrapped = multi or false b.TextXAlignment = Enum.TextXAlignment.Left b.TextYAlignment = multi and Enum.TextYAlignment.Top or Enum.TextYAlignment.Center b.BorderSizePixel = 0 b.Parent = parent return b end local function hoverSwap(btn, normal, hover) btn.MouseEnter:Connect(function() btn.BackgroundColor3 = hover end) btn.MouseLeave:Connect(function() btn.BackgroundColor3 = normal end) end local function sectionTitle(parent, text, y) return newLabel(parent, text, UDim2.new(1,0,0,13), UDim2.new(0,0,0,y), 9, Enum.Font.GothamBold, C.dimText) end -- ============================================================ -- ROOT GUI -- ============================================================ local existing = PlayerGui:FindFirstChild("ChatTranslator") if existing then existing:Destroy() end local screenGui = Instance.new("ScreenGui") screenGui.Name = "ChatTranslator" screenGui.ResetOnSpawn = false screenGui.ZIndexBehavior = Enum.ZIndexBehavior.Sibling screenGui.Parent = PlayerGui local function currentSize() return SIZES[Settings.windowSize] or SIZES.Normal end local sz = currentSize() local mainFrame = newFrame(screenGui, UDim2.new(0, sz.X, 0, sz.Y), UDim2.new(0.5, -sz.X/2, 0.5, -sz.Y/2), C.bg, 14) mainFrame.Active = true mainFrame.Draggable = true local shadow = newFrame(mainFrame, UDim2.new(1,20,1,20), UDim2.new(0,-10,0,8), Color3.fromRGB(0,0,0), 18) shadow.BackgroundTransparency = 0.65 shadow.ZIndex = mainFrame.ZIndex - 1 -- ============================================================ -- TITLE BAR -- ============================================================ local titleBar = newFrame(mainFrame, UDim2.new(1,0,0,46), UDim2.new(0,0,0,0), C.accent, 14) applyGradient(C.accent2, Color3.fromRGB(55,25,150), 90, titleBar) local titleBarFix = newFrame(titleBar, UDim2.new(1,0,0,14), UDim2.new(0,0,1,-14), Color3.fromRGB(55,25,150)) local killBtn = newButton(titleBar, "✕", UDim2.new(0,26,0,26), UDim2.new(0,10,0.5,-13), Color3.fromRGB(48,48,62), C.white, 12, Enum.Font.GothamBold) applyCorner(6, killBtn) hoverSwap(killBtn, Color3.fromRGB(48,48,62), Color3.fromRGB(190,45,45)) killBtn.MouseButton1Click:Connect(function() screenGui:Destroy() end) newLabel(titleBar, "🌐 Chat Translator", UDim2.new(1,-160,1,0), UDim2.new(0,44,0,0), 15, Enum.Font.GothamBold, C.white) local keyHintLabel = newLabel(titleBar, "", UDim2.new(0,115,0,16), UDim2.new(1,-120,0.5,-8), 9, Enum.Font.GothamBold, C.dimText, Enum.TextXAlignment.Right) local function refreshKeyHints() keyHintLabel.Text = Settings.toggleKey.Name .. " toggle • " .. Settings.chatKey.Name .. " chat" end refreshKeyHints() -- ============================================================ -- TAB BAR -- ============================================================ local tabBar = newFrame(mainFrame, UDim2.new(1,-24,0,32), UDim2.new(0,12,0,52), C.panel, 9) local tabButtons = {} local tabNames = { "translator", "log", "settings" } local tabLabels = { "✈ Translator", "💬 Log", "⚙ Settings" } for i, key in ipairs(tabNames) do local b = newButton(tabBar, tabLabels[i], UDim2.new(1/3,-2,1,-6), UDim2.new((i-1)/3, 1, 0, 3), C.panel, Color3.fromRGB(120,120,155), 11, Enum.Font.GothamBold) applyCorner(6, b) tabButtons[key] = b end -- ============================================================ -- PANELS -- ============================================================ local PANEL_Y = 90 local function getPanelSize() local s = currentSize() return UDim2.new(1,-24, 0, s.Y - PANEL_Y - 8) end local panels = {} for _, key in ipairs(tabNames) do local p = newFrame(mainFrame, getPanelSize(), UDim2.new(0,12,0,PANEL_Y), C.bg) p.BackgroundTransparency = 1 p.Visible = false panels[key] = p end local function switchTab(tab) activeTab = tab for k, p in pairs(panels) do p.Visible = (k == tab) end for k, b in pairs(tabButtons) do if k == tab then b.BackgroundColor3 = C.accent b.TextColor3 = C.white else b.BackgroundColor3 = C.panel b.TextColor3 = Color3.fromRGB(120,120,155) end end end for key, btn in pairs(tabButtons) do btn.MouseButton1Click:Connect(function() switchTab(key) end) end -- ============================================================ -- FLOATING CHAT BAR -- ============================================================ local floatBar = newFrame(mainFrame, UDim2.new(1,-24,0,48), UDim2.new(0,12,1,-56), Color3.fromRGB(15,10,30), 10) floatBar.Visible = false floatBar.ZIndex = 20 newFrame(floatBar, UDim2.new(1,0,0,2), UDim2.new(0,0,0,0), C.accent2, 2) local floatLangPill = newFrame(floatBar, UDim2.new(0,72,0,26), UDim2.new(0,6,0.5,-13), Color3.fromRGB(40,22,100), 6) local floatLangText = newLabel(floatLangPill, "no lang", UDim2.new(1,0,1,0), nil, 9, Enum.Font.GothamBold, Color3.fromRGB(160,130,255), Enum.TextXAlignment.Center) local floatInput = newTextBox(floatBar, "Type + Enter to translate • Esc to close", UDim2.new(1,-164,0,30), UDim2.new(0,84,0.5,-15), false) floatInput.ZIndex = 21 applyCorner(7, floatInput) applyPadding(8,8,0,0,floatInput) newLabel(floatBar, "Esc close", UDim2.new(0,62,0,14), UDim2.new(1,-68,0.5,-7), 9, Enum.Font.Gotham, C.dimText, Enum.TextXAlignment.Center) local floatStatus = newLabel(mainFrame, "", UDim2.new(1,-24,0,14), UDim2.new(0,12,1,-72), 9, Enum.Font.GothamBold, C.green, Enum.TextXAlignment.Center) floatStatus.Visible = false floatStatus.ZIndex = 20 local function setFloatStatus(ok, msg) floatStatus.TextColor3 = ok and C.green or C.red floatStatus.Text = msg floatStatus.Visible = true task.delay(4, function() floatStatus.Visible = false end) end local function showFloatBar(lang) if lang and lang ~= "" then floatLangText.Text = "→ " .. lang floatLangText.TextColor3 = Color3.fromRGB(160,130,255) else floatLangText.Text = "no lang set" floatLangText.TextColor3 = C.red end floatBar.Visible = true task.wait(0.05) floatInput:CaptureFocus() end local function hideFloatBar() floatBar.Visible = false floatInput.Text = "" floatPending = nil end -- ============================================================ -- ============================================================ -- TRANSLATOR PANEL -- ============================================================ -- ============================================================ local tp = panels.translator local toggleRow = newFrame(tp, UDim2.new(1,0,0,36), UDim2.new(0,0,0,0), C.panel, 9) local toggleLabel = newLabel(toggleRow, "🔄 Auto-translate my chats", UDim2.new(1,-72,1,0), UDim2.new(0,12,0,0), 12, Enum.Font.GothamBold, C.sub) local toggleTrack = newFrame(toggleRow, UDim2.new(0,44,0,22), UDim2.new(1,-54,0.5,-11), Color3.fromRGB(40,40,58), 99) local toggleKnob = newFrame(toggleTrack, UDim2.new(0,16,0,16), UDim2.new(0,3,0.5,-8), Color3.fromRGB(100,100,130), 99) local toggleHit = newButton(toggleTrack, "", UDim2.new(1,0,1,0), nil, Color3.fromRGB(0,0,0), C.white, 1) toggleHit.BackgroundTransparency = 1 local function setAutoTranslate(on) autoTranslate = on if on then toggleTrack.BackgroundColor3 = C.accent toggleKnob.Position = UDim2.new(1,-19,0.5,-8) toggleKnob.BackgroundColor3 = C.white toggleLabel.Text = "🔄 Auto-translate — ON" toggleLabel.TextColor3 = Color3.fromRGB(150,115,255) else toggleTrack.BackgroundColor3 = Color3.fromRGB(40,40,58) toggleKnob.Position = UDim2.new(0,3,0.5,-8) toggleKnob.BackgroundColor3 = Color3.fromRGB(100,100,130) toggleLabel.Text = "🔄 Auto-translate my chats" toggleLabel.TextColor3 = C.sub hideFloatBar() end end toggleHit.MouseButton1Click:Connect(function() setAutoTranslate(not autoTranslate) end) sectionTitle(tp, "TARGET LANGUAGE", 46) local langFrame = newFrame(tp, UDim2.new(1,0,0,78), UDim2.new(0,0,0,61), C.panel, 9) local langScroll = Instance.new("ScrollingFrame") langScroll.Size = UDim2.new(1,-8,1,-8) langScroll.Position = UDim2.new(0,4,0,4) langScroll.BackgroundTransparency = 1 langScroll.BorderSizePixel = 0 langScroll.ScrollBarThickness = 3 langScroll.ScrollBarImageColor3 = C.accent langScroll.CanvasSize = UDim2.new(0,0,0,0) langScroll.AutomaticCanvasSize = Enum.AutomaticSize.Y langScroll.Parent = langFrame local pillLayout = Instance.new("UIListLayout") pillLayout.Padding = UDim.new(0,4) pillLayout.FillDirection = Enum.FillDirection.Horizontal pillLayout.Wraps = true pillLayout.Parent = langScroll applyPadding(4,0,4,0,langScroll) sectionTitle(tp, "TRANSLATE TO", 147) local promptBox = newTextBox(tp, "e.g. Spanish, Arabic...", UDim2.new(1,0,0,32), UDim2.new(0,0,0,162)) applyCorner(8, promptBox) applyPadding(10,10,0,0,promptBox) promptBox:GetPropertyChangedSignal("Text"):Connect(function() local lang = promptBox.Text if lang ~= "" then floatLangText.Text = "→ " .. lang floatLangText.TextColor3 = Color3.fromRGB(160,130,255) else floatLangText.Text = "no lang set" floatLangText.TextColor3 = C.red end end) local selectedPill = nil for _, langName in ipairs(LANGUAGE_LIST) do local pill = newButton(langScroll, langName, UDim2.new(0,0,0,20), nil, Color3.fromRGB(28,28,46), Color3.fromRGB(165,165,205), 10) pill.AutomaticSize = Enum.AutomaticSize.X applyCorner(99, pill) applyPadding(9,9,0,0,pill) pill.MouseButton1Click:Connect(function() if selectedPill then selectedPill.BackgroundColor3 = Color3.fromRGB(28,28,46) selectedPill.TextColor3 = Color3.fromRGB(165,165,205) end pill.BackgroundColor3 = C.accent pill.TextColor3 = C.white selectedPill = pill promptBox.Text = langName end) end sectionTitle(tp, "YOUR MESSAGE", 202) local inputBox = newTextBox(tp, "Type your message here...", UDim2.new(1,0,0,60), UDim2.new(0,0,0,217), true) applyCorner(8, inputBox) applyPadding(10,10,8,0,inputBox) sectionTitle(tp, "TRANSLATION PREVIEW", 285) local previewLabel = newLabel(tp, "Translation will appear here...", UDim2.new(1,0,0,46), UDim2.new(0,0,0,300), 12, Enum.Font.Gotham, Color3.fromRGB(130,130,165)) previewLabel.BackgroundColor3 = C.input previewLabel.BackgroundTransparency = 0 previewLabel.TextYAlignment = Enum.TextYAlignment.Top applyCorner(8, previewLabel) applyPadding(10,10,8,0,previewLabel) local statusLabel = newLabel(tp, "", UDim2.new(1,0,0,14), UDim2.new(0,0,0,352), 10, Enum.Font.Gotham, C.green) local function setStatus(ok, msg) statusLabel.TextColor3 = ok and C.green or C.red statusLabel.Text = msg end local sendBtn = newButton(tp, "✈ Send", UDim2.new(0.42,-3,0,36), UDim2.new(0,0,0,368), C.accent, C.white, 12, Enum.Font.GothamBold) applyCorner(8, sendBtn) applyGradient(C.accent2, Color3.fromRGB(58,22,158), 90, sendBtn) hoverSwap(sendBtn, C.accent, Color3.fromRGB(100,52,210)) local transOnlyBtn = newButton(tp, "🔍 Translate", UDim2.new(0.33,-3,0,36), UDim2.new(0.42,3,0,368), Color3.fromRGB(26,70,50), Color3.fromRGB(85,225,140), 12, Enum.Font.GothamBold) applyCorner(8, transOnlyBtn) hoverSwap(transOnlyBtn, Color3.fromRGB(26,70,50), Color3.fromRGB(36,92,66)) local copyBtn = newButton(tp, "📋 Copy", UDim2.new(0.25,-2,0,36), UDim2.new(0.75,2,0,368), Color3.fromRGB(24,24,40), Color3.fromRGB(180,180,215), 12, Enum.Font.GothamBold) applyCorner(8, copyBtn) hoverSwap(copyBtn, Color3.fromRGB(24,24,40), Color3.fromRGB(40,40,62)) local translating = false local function doTranslate(onDone) if translating then return end local msg = inputBox.Text local lang = promptBox.Text if msg == "" then setStatus(false, "⚠ Type a message first.") return end if lang == "" then setStatus(false, "⚠ Set a target language.") return end translating = true sendBtn.Active = false transOnlyBtn.Active = false copyBtn.Active = false previewLabel.Text = "Translating..." previewLabel.TextColor3 = Color3.fromRGB(110,110,150) statusLabel.Text = "" task.spawn(function() local result = translateToLang(msg, lang) translating = false sendBtn.Active = true transOnlyBtn.Active = true copyBtn.Active = true if result and result ~= "" then lastTranslated = result previewLabel.Text = result previewLabel.TextColor3 = C.text onDone(result) else previewLabel.Text = "Translation failed." previewLabel.TextColor3 = C.red setStatus(false, "✗ Ollama unreachable — is the proxy running?") end end) end sendBtn.MouseButton1Click:Connect(function() doTranslate(function(r) if sendChat(r) then setStatus(true, "✓ Sent in " .. promptBox.Text .. "!") inputBox.Text = "" lastTranslated = "" else setStatus(false, "⚠ Send failed — try Copy.") end end) end) transOnlyBtn.MouseButton1Click:Connect(function() doTranslate(function(_) setStatus(true, "✓ Translated — not sent.") end) end) copyBtn.MouseButton1Click:Connect(function() if lastTranslated == "" then doTranslate(function(_) local ok = copyToClipboard(lastTranslated) setStatus(ok, ok and "✓ Copied!" or "✗ Clipboard unavailable.") end) else local ok = copyToClipboard(lastTranslated) setStatus(ok, ok and "✓ Copied!" or "✗ Clipboard unavailable.") end end) -- ============================================================ -- ============================================================ -- LOG PANEL -- ============================================================ -- ============================================================ local lp = panels.log local logTopBar = newFrame(lp, UDim2.new(1,0,0,26), UDim2.new(0,0,0,0), C.panel, 8) local logCountLabel = newLabel(logTopBar, "0 messages logged", UDim2.new(1,-80,1,0), UDim2.new(0,12,0,0), 10, Enum.Font.GothamBold, C.dimText) local clearBtn = newButton(logTopBar, "🗑 Clear", UDim2.new(0,62,0,18), UDim2.new(1,-66,0.5,-9), Color3.fromRGB(62,26,26), Color3.fromRGB(210,120,120), 10) applyCorner(5, clearBtn) hoverSwap(clearBtn, Color3.fromRGB(62,26,26), Color3.fromRGB(90,35,35)) local logOuter = newFrame(lp, UDim2.new(1,0,1,-32), UDim2.new(0,0,0,30), Color3.fromRGB(13,13,21), 10) local logScroll = Instance.new("ScrollingFrame") logScroll.Size = UDim2.new(1,-6,1,-8) logScroll.Position = UDim2.new(0,3,0,4) logScroll.BackgroundTransparency = 1 logScroll.BorderSizePixel = 0 logScroll.ScrollBarThickness = 3 logScroll.ScrollBarImageColor3 = C.accent logScroll.CanvasSize = UDim2.new(0,0,0,0) logScroll.ScrollingDirection = Enum.ScrollingDirection.Y logScroll.Parent = logOuter local logListLayout = Instance.new("UIListLayout") logListLayout.Padding = UDim.new(0,4) logListLayout.SortOrder = Enum.SortOrder.LayoutOrder logListLayout.Parent = logScroll applyPadding(4,4,4,4,logScroll) local function scrollToBottom() local h = logListLayout.AbsoluteContentSize.Y + 12 logScroll.CanvasSize = UDim2.new(0,0,0,h) logScroll.CanvasPosition = Vector2.new(0, math.max(0, h - logScroll.AbsoluteSize.Y)) end clearBtn.MouseButton1Click:Connect(function() for _, c in ipairs(logScroll:GetChildren()) do if c:IsA("Frame") then c:Destroy() end end logEntryCount = 0 logTotalCount = 0 logCountLabel.Text = "0 messages logged" logScroll.CanvasSize = UDim2.new(0,0,0,0) end) -- ============================================================ -- LOG CARD BUILDERS -- ============================================================ local CPL = 42 local function countLines(text) return math.max(1, math.ceil(#text / CPL)) end local function createPendingCard(playerName, originalMsg) logEntryCount += 1 local pc = getPlayerColor(playerName) local card = newFrame(logScroll, UDim2.new(1,0,0,52), nil, Color3.fromRGB(17,17,27), 8) card.LayoutOrder = logEntryCount newFrame(card, UDim2.new(0,3,1,-10), UDim2.new(0,0,0,5), pc, 2) newLabel(card, playerName, UDim2.new(1,-96,0,14), UDim2.new(0,10,0,5), 11, Enum.Font.GothamBold, pc) local badgeFrame = newFrame(card, UDim2.new(0,86,0,14), UDim2.new(1,-90,0,5), Color3.fromRGB(38,32,62), 4) newLabel(badgeFrame, "⏳ translating...", UDim2.new(1,0,1,0), nil, 9, Enum.Font.GothamBold, Color3.fromRGB(135,105,230), Enum.TextXAlignment.Center) newLabel(card, originalMsg, UDim2.new(1,-16,0,28), UDim2.new(0,10,0,22), 11, Enum.Font.Gotham, Color3.fromRGB(150,145,185)) task.delay(0.05, scrollToBottom) return card end local function finishCard(card, playerName, originalMsg, translatedMsg, langName, stamp) if not card or not card.Parent then return end local pc = getPlayerColor(playerName) local origClean = originalMsg:lower():match("^%s*(.-)%s*$") or "" local transClean = translatedMsg:lower():match("^%s*(.-)%s*$") or "" local hasTrans = (origClean ~= transClean) and translatedMsg ~= "" local oLines = countLines(originalMsg) local tLines = hasTrans and countLines("→ " .. translatedMsg) or 0 local h = 5 + 14 + 4 + (oLines*14) + (hasTrans and (10 + tLines*14) or 0) + 12 + 9 card.Size = UDim2.new(1,0,0,h) card.BackgroundColor3 = Color3.fromRGB(17,17,28) for _, child in ipairs(card:GetChildren()) do child:Destroy() end newFrame(card, UDim2.new(0,3,1,-10), UDim2.new(0,0,0,5), pc, 2) newLabel(card, playerName, UDim2.new(1,-90,0,14), UDim2.new(0,10,0,5), 11, Enum.Font.GothamBold, pc) local badge = newFrame(card, UDim2.new(0,72,0,14), UDim2.new(1,-76,0,5), Color3.fromRGB(58,30,145), 4) newLabel(badge, langName, UDim2.new(1,0,1,0), nil, 9, Enum.Font.GothamBold, Color3.fromRGB(190,172,255), Enum.TextXAlignment.Center) local yPos = 23 newLabel(card, originalMsg, UDim2.new(1,-16,0,oLines*14), UDim2.new(0,10,0,yPos), 11, Enum.Font.Gotham, Color3.fromRGB(115,110,150)) yPos = yPos + oLines*14 if hasTrans then yPos = yPos + 3 newFrame(card, UDim2.new(1,-20,0,1), UDim2.new(0,10,0,yPos), Color3.fromRGB(48,38,78)) yPos = yPos + 7 newLabel(card, "→ " .. translatedMsg, UDim2.new(1,-16,0,tLines*14), UDim2.new(0,10,0,yPos), 12, Enum.Font.GothamBold, Color3.fromRGB(215,215,240)) yPos = yPos + tLines*14 end yPos = yPos + 4 newLabel(card, stamp, UDim2.new(1,-16,0,12), UDim2.new(0,10,0,yPos), 9, Enum.Font.Gotham, Color3.fromRGB(55,50,85)) logTotalCount += 1 logCountLabel.Text = logTotalCount .. " message" .. (logTotalCount == 1 and "" or "s") .. " logged" task.delay(0.05, scrollToBottom) end -- ============================================================ -- LOG QUEUE -- ============================================================ local function processLogQueue() if logRunning then return end logRunning = true task.spawn(function() while #logQueue > 0 do local item = table.remove(logQueue, 1) local card = createPendingCard(item.name, item.msg) local trans = translateToEnglish(item.msg) local lang = detectLanguage(item.msg) finishCard(card, item.name, item.msg, trans, lang, item.ts) end logRunning = false end) end local function queueMessage(playerName, msg) if not playerName or playerName == "" then return end if not msg or msg == "" then return end if isDuplicate(playerName, msg) then return end table.insert(logQueue, { name = playerName, msg = msg, ts = getTimestamp() }) processLogQueue() end -- ============================================================ -- CHAT LISTENER -- ============================================================ local chatHooked = false pcall(function() if TextChatService.ChatVersion == Enum.ChatVersion.TextChatService then TextChatService.MessageReceived:Connect(function(msgObj) if not msgObj then return end if not msgObj.Text or msgObj.Text == "" then return end local src = msgObj.TextSource if not src then return end local srcName = src.Name if not srcName or srcName == "" then return end local player = Players:FindFirstChild(srcName) queueMessage(player and player.Name or srcName, msgObj.Text) end) chatHooked = true print("[CT] Hooked TextChatService") end end) if not chatHooked then pcall(function() local function hookPlayer(p) if not p then return end p.Chatted:Connect(function(msg) if msg and msg ~= "" then queueMessage(p.Name, msg) end end) end for _, p in ipairs(Players:GetPlayers()) do hookPlayer(p) end Players.PlayerAdded:Connect(hookPlayer) chatHooked = true print("[CT] Hooked legacy Chatted") end) end if not chatHooked then warn("[CT] Could not hook any chat system!") end -- ============================================================ -- ============================================================ -- SETTINGS PANEL -- ============================================================ -- ============================================================ local sp = panels.settings local function buildRebindRow(parent, yOffset, labelText, getKey, setKey) newLabel(parent, labelText, UDim2.new(1,0,0,13), UDim2.new(0,0,0,yOffset), 10, Enum.Font.Gotham, C.sub) local row = newFrame(parent, UDim2.new(1,0,0,28), UDim2.new(0,0,0,yOffset+15), C.panel, 7) local keyLabel = newLabel(row, getKey().Name, UDim2.new(1,-80,1,0), UDim2.new(0,10,0,0), 11, Enum.Font.GothamBold, C.text) local changeBtn = newButton(row, "Change", UDim2.new(0,66,0,18), UDim2.new(1,-70,0.5,-9), C.accent, C.white, 10) applyCorner(5, changeBtn) hoverSwap(changeBtn, C.accent, C.accent2) local listening = false changeBtn.MouseButton1Click:Connect(function() if listening then return end listening = true changeBtn.Text = "Press key..." changeBtn.BackgroundColor3 = C.yellow local conn conn = UIS.InputBegan:Connect(function(input) if input.UserInputType ~= Enum.UserInputType.Keyboard then return end conn:Disconnect() setKey(input.KeyCode) keyLabel.Text = input.KeyCode.Name changeBtn.Text = "Change" changeBtn.BackgroundColor3 = C.accent listening = false refreshKeyHints() end) end) return yOffset + 50 end local sy = 0 newLabel(sp, "⚙ Settings", UDim2.new(1,0,0,22), UDim2.new(0,0,0,sy), 16, Enum.Font.GothamBold, C.text) sy = sy + 30 newFrame(sp, UDim2.new(1,0,0,1), UDim2.new(0,0,0,sy), C.border) sy = sy + 10 sectionTitle(sp, "WINDOW SIZE", sy) sy = sy + 16 local sizeRow = newFrame(sp, UDim2.new(1,0,0,30), UDim2.new(0,0,0,sy), C.panel, 8) sy = sy + 38 local sizeBtns = {} for i, sName in ipairs({"Small","Normal","Large"}) do local isActive = (sName == Settings.windowSize) local sb = newButton(sizeRow, sName, UDim2.new(1/3,-2,1,-6), UDim2.new((i-1)/3,1,0,3), isActive and C.accent or C.input, isActive and C.white or C.sub, 11, Enum.Font.GothamBold) applyCorner(6, sb) sizeBtns[sName] = sb sb.MouseButton1Click:Connect(function() Settings.windowSize = sName local ns = SIZES[sName] mainFrame.Size = UDim2.new(0, ns.X, 0, ns.Y) mainFrame.Position = UDim2.new(0.5, -ns.X/2, 0.5, -ns.Y/2) for _, pnl in pairs(panels) do pnl.Size = getPanelSize() end for k2, b2 in pairs(sizeBtns) do local a = (k2 == sName) b2.BackgroundColor3 = a and C.accent or C.input b2.TextColor3 = a and C.white or C.sub end end) end newFrame(sp, UDim2.new(1,0,0,1), UDim2.new(0,0,0,sy), C.border) sy = sy + 10 sectionTitle(sp, "HOTKEYS", sy) sy = sy + 16 sy = buildRebindRow(sp, sy, "Toggle GUI visibility", function() return Settings.toggleKey end, function(k) Settings.toggleKey = k end) sy = buildRebindRow(sp, sy, "Focus chat input (auto-translate)", function() return Settings.chatKey end, function(k) Settings.chatKey = k end) newFrame(sp, UDim2.new(1,0,0,1), UDim2.new(0,0,0,sy), C.border) sy = sy + 10 sectionTitle(sp, "OLLAMA", sy) sy = sy + 16 newLabel(sp, "Host", UDim2.new(1,0,0,13), UDim2.new(0,0,0,sy), 10, Enum.Font.Gotham, C.sub) sy = sy + 15 local hostBox = newTextBox(sp, "http://127.0.0.1:3000", UDim2.new(1,0,0,28), UDim2.new(0,0,0,sy)) hostBox.Text = Settings.ollamaHost applyCorner(7, hostBox) applyPadding(8,8,0,0,hostBox) hostBox:GetPropertyChangedSignal("Text"):Connect(function() if hostBox.Text ~= "" then Settings.ollamaHost = hostBox.Text end end) sy = sy + 34 newLabel(sp, "Model", UDim2.new(1,0,0,13), UDim2.new(0,0,0,sy), 10, Enum.Font.Gotham, C.sub) sy = sy + 15 local modelBox = newTextBox(sp, "qwen2.5:1.5b", UDim2.new(1,0,0,28), UDim2.new(0,0,0,sy)) modelBox.Text = Settings.ollamaModel applyCorner(7, modelBox) applyPadding(8,8,0,0,modelBox) modelBox:GetPropertyChangedSignal("Text"):Connect(function() if modelBox.Text ~= "" then Settings.ollamaModel = modelBox.Text end end) sy = sy + 34 -- ============================================================ -- SETTINGS: ENTER KEY BEHAVIOUR -- ============================================================ newFrame(sp, UDim2.new(1,0,0,1), UDim2.new(0,0,0,sy), C.border) sy = sy + 10 sectionTitle(sp, "ENTER KEY BEHAVIOUR", sy) sy = sy + 16 local enterModeRow = newFrame(sp, UDim2.new(1,0,0,30), UDim2.new(0,0,0,sy), C.panel, 8) sy = sy + 38 local enterModeBtns = {} local enterModeDesc = newLabel(sp, "", UDim2.new(1,0,0,28), UDim2.new(0,0,0,sy), 9, Enum.Font.Gotham, C.dimText) local function setEnterMode(mode) Settings.enterMode = mode pendingSend = nil floatPending = nil for k, b in pairs(enterModeBtns) do local a = (k == mode) b.BackgroundColor3 = a and C.accent or C.input b.TextColor3 = a and C.white or C.sub end if mode == "preview" then enterModeDesc.Text = "Enter → translate + copy. Enter again → send." else enterModeDesc.Text = "Enter → translate and send immediately." end end for i, em in ipairs({ {key="preview",label="Preview + Copy"}, {key="send",label="Send Directly"} }) do local isActive = (Settings.enterMode == em.key) local eb = newButton(enterModeRow, em.label, UDim2.new(0.5,-2,1,-6), UDim2.new((i-1)*0.5,1,0,3), isActive and C.accent or C.input, isActive and C.white or C.sub, 11, Enum.Font.GothamBold) applyCorner(6, eb) enterModeBtns[em.key] = eb eb.MouseButton1Click:Connect(function() setEnterMode(em.key) end) end setEnterMode(Settings.enterMode) -- populate description on load -- ============================================================ -- FLOAT BAR SUBMIT -- Preview mode: 1st Enter → translate + copy, keep bar open for 2nd Enter → send -- Send mode: Enter → translate + send immediately -- ============================================================ floatInput.FocusLost:Connect(function(enterPressed) local msg = floatInput.Text local lang = promptBox.Text if not enterPressed then hideFloatBar() return end -- ── PREVIEW MODE ────────────────────────────────────────── if Settings.enterMode == "preview" then -- second Enter: send the pending translation if floatPending then local toSend = floatPending floatPending = nil floatInput.Text = "" hideFloatBar() task.spawn(function() sendChat(toSend) setFloatStatus(true, "✓ Sent in " .. lang .. "!") end) return end -- first Enter: translate + copy, keep bar open if msg == "" then return end if lang == "" then setFloatStatus(false, "⚠ Set a target language in the Translator tab first.") task.wait(0.05); floatInput:CaptureFocus() return end floatInput.Text = "" setFloatStatus(false, "⏳ Translating...") task.spawn(function() local translated = translateToLang(msg, lang) if translated and translated ~= "" then floatPending = translated copyToClipboard(translated) setFloatStatus(true, "📋 " .. translated .. " — Enter to send • Esc to cancel") task.wait(0.05) if floatBar.Visible then floatInput:CaptureFocus() end else floatPending = nil setFloatStatus(false, "✗ Translation failed.") task.wait(0.05) if floatBar.Visible then floatInput:CaptureFocus() end end end) return end -- ── SEND MODE ───────────────────────────────────────────── floatInput.Text = "" hideFloatBar() if msg == "" then return end if lang == "" then setFloatStatus(false, "⚠ Set a target language in the Translator tab first.") return end setFloatStatus(false, "⏳ Translating...") task.spawn(function() local translated = translateToLang(msg, lang) if translated and translated ~= "" then sendChat(translated) setFloatStatus(true, "✓ Sent in " .. lang .. "!") else sendChat(msg) setFloatStatus(false, "✗ Translation failed — sent original.") end end) end) -- ============================================================ -- GLOBAL KEYBINDS -- ============================================================ UIS.InputBegan:Connect(function(input, gameProcessed) if input.UserInputType ~= Enum.UserInputType.Keyboard then return end -- Toggle GUI if input.KeyCode == Settings.toggleKey then guiVisible = not guiVisible mainFrame.Visible = guiVisible if not guiVisible then hideFloatBar() end return end -- Escape: dismiss float bar (also clears any pending send) if input.KeyCode == Enum.KeyCode.Escape and floatBar.Visible then hideFloatBar() return end -- Chat key: works from any tab if input.KeyCode == Settings.chatKey and autoTranslate and not gameProcessed then mainFrame.Visible = true guiVisible = true if activeTab == "translator" then task.wait(0.05) inputBox:CaptureFocus() else showFloatBar(promptBox.Text) end return end -- Enter while main inputBox is focused if input.KeyCode == Enum.KeyCode.Return and inputBox:IsFocused() then local msg = inputBox.Text local lang = promptBox.Text -- ── PREVIEW MODE ────────────────────────────────────── if Settings.enterMode == "preview" then -- second Enter on empty box = send pending if msg == "" then if pendingSend then local toSend = pendingSend pendingSend = nil inputBox:ReleaseFocus(false) setStatus(false, "⏳ Sending...") task.spawn(function() sendChat(toSend) setStatus(true, "✓ Sent in " .. lang .. "!") end) end return end if lang == "" then setStatus(false, "⚠ Set a target language first.") return end pendingSend = nil inputBox.Text = "" statusLabel.Text = "⏳ Translating..." statusLabel.TextColor3 = Color3.fromRGB(135,105,230) task.spawn(function() local translated = translateToLang(msg, lang) if translated and translated ~= "" then pendingSend = translated copyToClipboard(translated) previewLabel.Text = translated previewLabel.TextColor3 = C.text setStatus(true, "📋 Copied — Enter again to send") task.wait(0.05); inputBox:CaptureFocus() else pendingSend = nil previewLabel.Text = "Translation failed." previewLabel.TextColor3 = C.red setStatus(false, "✗ Ollama unreachable — is the proxy running?") end end) return end -- ── SEND MODE ───────────────────────────────────────── if msg == "" then return end if lang == "" then setStatus(false, "⚠ Set a target language first.") return end inputBox.Text = "" inputBox:ReleaseFocus(false) statusLabel.Text = "⏳ Translating..." statusLabel.TextColor3 = Color3.fromRGB(135,105,230) task.spawn(function() local translated = translateToLang(msg, lang) if translated and translated ~= "" then sendChat(translated) setStatus(true, "✓ Sent in " .. lang .. "!") else sendChat(msg) setStatus(false, "✗ Failed — sent original.") end end) end end) -- ============================================================ -- INIT -- ============================================================ switchTab("translator") print("[CT] Chat Translator loaded — " .. Settings.toggleKey.Name .. " toggle | " .. Settings.chatKey.Name .. " chat (works from any tab)")