Translator GUI
Key RequiredUniversal

Translator GUI

Universal ScriptGuest_43854Guest_43854 1.4K views 1.9K executesUpdated Mar 16, 2026
5.0/ 5(2 votes)
20100% Positive
Lua Script•1362 lines (46475 chars)
-- ============================================================
-- 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)")

Features & Information

- THIS SCRIPT HAS ONLY BEEN TESTED IN WINDOWS. I DO NOT THINK MY SCRIPTS WOULD WORK ON ANY MOBILE DEVICE. - This is a claude-made GUI that can automatically translate your scripts using ollama to run AI models to translate for you. Requirements: Any Executor Node.JS Python Ollama Once you have all of them installed, you must open command prompt or terminal and put one of these in: ollama pull qwen2.5:1.5b or ollama pull qwen2.5:7b You can choose between one of these models in the settings. For more accuracy, choose 7b. For more speed (which I recommend), choose 1.5b. Once ollama is finished pulling the small language models, you can use an automated app I made for launching the servers. This requires python, which is why I asked you to download it. Note: This app is made entirely in python so if you own any anti-virus, it will ask to scan it. It shouldn't be flagged since it isn't malware. https://www.mediafire.com/file/ci3lwhri7gcnfqi/OllamaLauncher.exe/file Here is the virus total: https://www.virustotal.com/gui/file/f44f198c743ab85e94f7e900b24908696f07642347676fd7c6a0c1ea4c077f97/detection This only detects as "Suspicious" since it is used to run the local ollama server on your computer. It isn't flagged by major antiviruses such as Kaspersky, Norton, nor MalwareBytes since they can test the app in a sandbox easier and note it isn't malware. Note: Most times it will fail when running the server, this is most likely going to look like this error: [ollama] Error: listen tcp 127.0.0.1:11434: bind: Only one usage of each socket address (protocol/network address/port) is normally permitted. The reason this error occurs is because you have ollama running in the background. To exit the program: Open task manager, manually find ollama, and kill all processes named ollama. Then, try to run the server. It should run now. Once the server is running (don't mind red, red doesn't always mean errors), you will see the proxy beginning to run locally. What you can do now is join any roblox game with your executor. Then, execute this script into the game. You will see a gui for translating. I made some settings that you can tweak around with. Have fun! Also, it is prompt based so yes, theoretically, you can make it speak any language.
#Translator#Translate#AI#Speaking#Language

Community Comments (5)

Guest_43854Guest_43854Mar 16, 2026

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.

KyeliszKyeliszMar 17, 2026

sorry but i would like you to add a loadstring

Replying to @Kyelisz
Guest_43854Guest_43854Mar 23, 2026

@Dorean you can't really do loadstring with this since it has settings that you may have to tweak.

luisgamercoolerluisgamercoolerJul 4, 2026

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"

Replying to @luisgamercooler
luisgamercoolerluisgamercoolerJul 4, 2026

to note i downloaded all the requirements you put in the description

More Scripts for Universal Scripts