DEV Community

Cover image for How I Built TERMINAL47: Anonymous Real-Time Chat with Live Translation (Next.js + Socket.io + TypeScript + Express)
Blackwatch
Blackwatch

Posted on • Edited on

How I Built TERMINAL47: Anonymous Real-Time Chat with Live Translation (Next.js + Socket.io + TypeScript + Express)

Zero-friction chat app—no signup, link-only access, anonymous "Agent-X" names, set duration for room, auto room dispose once time is over, no data is saved/stored, pure anonymity, live language switching (history bulk-translates + new messages stream). Hacker terminal UI. V1 complete github


Live chat with users sidebar, countdown timer, translation status.

Hey devs!, I wanted to level up my skills, so I combined Socket.io + real-time multilingual chat + cyberpunk UI. Meet TERMINAL47.

The Flow: Share link → Join as anonymous agent → Chat → Switch English→Japanese mid-convo → All past messages instantly translate → New messages are translated in real time →Timer expires → Room erased forever.

🛠️ Tech Stack (Production Architecture)

Frontend: Next.js 16 + TypeScript 
Backend: Express + Socket.io + Lingo.dev SDK
State: useState (local) + localStorage (userName)
Storage: None (pure ephemeral)
Deployment: Local dev → GitHub
Enter fullscreen mode Exit fullscreen mode

Step 1: Backend Setup (index.js)
Clean Express + Socket.io server with dedicated translation endpoints:

// index.js
import express from "express";
import { createServer } from "http";
import { Server } from "socket.io";
import socketEvent from "./socket/events.js"; // Room logic here
import languageTranslationBulk from "./utils/translationBulk.js";
import languageTranslationSingle from "./utils/translationSingle.js";

const app = express();
const server = createServer(app);
const io = new Server(server, { cors: { origin: process.env.CLIENT_URL }});

app.use(cors());
app.use(express.json());


app.post("/auth/translation/bulk", async (req, res) => {
  const { messages, currentLanguage, translateTo } = req.body;
  const languageTranslationFunction = await languageTranslationBulk(
    messages, currentLanguage, translateTo
  );
  res.json({ success: true, data: languageTranslationFunction });
});

app.post("/auth/translation/chunk", async (req, res) => {
  const { message, currentLanguage, translateTo } = req.body;
  const languageTranslationFunction = await languageTranslationSingle(
    message, currentLanguage, translateTo
  );
  res.json({ success: true, data: languageTranslationFunction });
});

socketEvent(io); // Room management
Enter fullscreen mode Exit fullscreen mode

Step 2: Lingo.dev Integration
Bulk translation (history on language switch):

// utils/translationBulk.js -
import { LingoDotDevEngine } from "lingo.dev/sdk";

const languageTranslationBulk = async (messages, currentLanguage, translateTo) => {
  const lingoDotDev = new LingoDotDevEngine({ apiKey: process.env.LINGO_DEV_API_KEY });
  const userMessages = messages.filter((msg) => msg.text != "");

  const translated = await lingoDotDev.localizeText(userMessages, {
    sourceLocale: currentLanguage,
    targetLocale: translateTo,
  });
  return translated;
};
Enter fullscreen mode Exit fullscreen mode

Step 3: Frontend Hook (useChat.ts)

// hooks/useChat.ts
export const useChat = () => {
  const [socket, setSocket] = useState<Socket | null>(null);
  const [roomStatus, setRoomStatus] = useState<boolean | null>(null);
  const [expiresAt, setExpiresAt] = useState<number | null>(null);

  const joinRoom = useCallback((roomId: string, userName: string) => {
    socket?.emit("join_room", { roomId, userName }, (result: any) => {
      if (result.success && result.room) {
        const expiry = Date.now() + result.room.timeRemaining;
        setExpiresAt(expiry);
      }
    });
  }, [socket]);

  const sendMessage = useCallback((roomId: string, message: string) => {
    socket?.emit("send_message", { roomId, message });
  }, [socket]);

  return { socket, joinRoom, sendMessage, roomStatus, expiresAt };
};
Enter fullscreen mode Exit fullscreen mode

Step 4: 🔥 Live Translation Magic (Chat.tsx)
Hero feature—language switch + bulk/single translation:

useEffect(() => {
  if (!allMessages.length || language === lastLanguage) return;

  setIsTranslating(true);
  const translateChat = async () => {
    const response = await fetch(`${process.env.NEXT_PUBLIC_BACKEND_URL}/auth/translation/bulk`, {
      method: "POST",
      body: JSON.stringify({
        messages: allMessages,
        currentLanguage: lastLanguage,
        translateTo: language,
      }),
    });
    const data = await response.json();
    setAllMessages(data.data); // All history translated!
  };
  translateChat();
}, [language]);
Enter fullscreen mode Exit fullscreen mode


Switch language → History bulk-translates + new messages auto-translate.

Step 5: SideNavBar (Users + Timer)

// SideNavBar.tsx - Key features
const SideNavBar = ({ userCount, expiresAt, language, setLanguage, isTranslating }) => {
  const [formattedTime, setFormattedTime] = useState("00:00");

  useEffect(() => {
    if (!expiresAt) return;
    const tick = () => {
      const remainingMs = expiresAt - Date.now();
      const totalSeconds = Math.max(0, Math.floor(remainingMs / 1000));
      const minutes = Math.floor(totalSeconds / 60);
      setFormattedTime(`${minutes.toString().padStart(2, '0')}:${(totalSeconds % 60).toString().padStart(2, '0')}`);
    };
    tick();
    const interval = setInterval(tick, 1000);
    return () => clearInterval(interval);
  }, [expiresAt]);

  return (
    <aside className="w-12 md:w-64 bg-secondaryBackground">
      <div>USERS- {userCount}</div>
      <LanguageSelection language={language} setLanguage={setLanguage} />
      <div className="timer">{formattedTime}</div> {/* Red border when <60s */}
    </aside>
  );
};
Enter fullscreen mode Exit fullscreen mode

🚀 V1 Complete ✅
✅ Real-time chat (Socket.io)
✅ User presence + system messages
✅ Live translation (bulk + streaming)
✅ Room expiry countdown
✅ Hacker terminal UI
✅ Anonymous access (no auth)
✅ TypeScript everywhere

Next: Typing indicators with Redis V2? Clone the repo and try language switching—feels like magic! 🪄
Github

Top comments (0)