
Key RequiredUniversal

-- ============================================================
-- 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)")



I made this video showcasing this script: https://www.youtube.com/watch?v=_rty5aET4fI&t=4s If anyone needs a tutorial on how to set it up, ask any time.
sorry but i would like you to add a loadstring
@Dorean you can't really do loadstring with this since it has settings that you may have to tweak.
having a bit of a error with the launcher " proxy.js not found at: C:\Users\Matthew\OneDrive\Do*ents\Projects\LuaTranslator\proxy.js [01:09:06] [sys] Edit PROXY_SCRIPT path at the top of launcher.py"
to note i downloaded all the requirements you put in the description