The Problem Nobody Wants to Solve
Going multilingual is one of the most commonly requested — and most commonly abandoned — features in software development.
Companies know they need it. Users demand it. Global markets require it. Revenue depends on it. But the implementation consistently defeats engineering teams.
Why? Because translation is only 10% of the work. The other 90% is orchestration:
-
String externalization — wrapping every hardcoded string in
t()functions across hundreds of files - Configuration — setting up i18n libraries, locale directories, provider wrappers, config files
- Translation management — maintaining JSON locale files that drift out of sync with every feature
- Deployment — creating branches, opening PRs, setting up preview environments to verify the result
- Coordination — developers, translators, reviewers, and PMs rarely speaking the same language about the problem
Retrofitting i18n into an existing codebase costs 3–10x more than building it in from the start. So teams punt. And the feature dies on the backlog.
AI tools like Lingo.dev have dramatically lowered the cost of the translation step itself. But they still require a developer to sit down, read the documentation, configure the tooling, run the CLI, manage the output, and set up a preview. That's still hours of focused engineering time per project.
The real problem isn't translation. Translation is solved. The problem is the orchestration gap — the hours between "we want multilingual support" and "here's a working branch with a live preview."
That's the gap LingoAgent fills.
What LingoAgent Does
One input. One click. One working multilingual branch with a live preview.
You provide a GitHub repository URL and select your target languages. An autonomous AI agent takes over completely:
- Clones the repo into an isolated cloud sandbox
- Detects the framework (Next.js App Router)
- Analyzes the codebase for existing i18n setups
- Configures the i18n scaffolding (providers, switcher, components)
- Extracts every hardcoded JSX string via Babel AST and translates them with Lingo.dev SDK
- Commits everything to a new branch and opens a GitHub PR
- Triggers a live Vercel preview deployment
The entire process takes few minutes instead of days. The developer's only job is to review the PR.
🌐 Live: lingo-agent.vercel.app
📦 Repo: github.com/Kashif-Rezwi/lingo-agent
The Approach: Orchestration, Not Translation
This distinction drove every design decision.
Lingo.dev has already solved the hard problem of accurate, context-aware AI translation. LingoAgent doesn't compete with that — it wraps Lingo.dev in an intelligent agent pipeline that removes every remaining manual step.
What Lingo.dev provides (5 tools)
| Tool | What It Does |
|---|---|
| Compiler | Build-time AST translation for React — zero code changes |
| CLI | Translates project files across 26 formats via i18n.json config |
| CI/CD Action | GitHub Action that automates CLI on every push |
| SDK | Runtime translation in 7+ languages for dynamic content |
| MCP Server | Exposes framework-specific setup knowledge to AI assistants |
What LingoAgent adds on top
| What Lingo.dev Still Needs a Human For | What LingoAgent Does Instead |
|---|---|
| Reading and understanding the docs | Agent queries the MCP server for exact instructions |
| Cloning the repo locally | Agent clones into an isolated E2B sandbox |
| Detecting framework & choosing setup path | Agent reads package.json and config files |
Modifying next.config.ts and layout.tsx
|
Agent applies verified changes from MCP |
Writing i18n.json configuration |
Agent generates it from user's selected locales |
Running npm install and translation engine |
Agent executes inside the sandbox |
| Creating branch and opening PR | Agent calls GitHub API via Octokit |
| Setting up preview deployment | Agent triggers Vercel and polls until ready |
LingoAgent uses the Lingo.dev SDK for string translation and the MCP Server for correct i18n scaffolding, building an autonomous pipeline around them.
Architecture: Three Layers
The system has three distinct layers:
┌──────────────────────────────────────────────────────┐
│ Browser (User) │
│ │
│ Next.js 14 App Router (Client) │
│ ┌──────────┬────────────┬────────────────────────┐ │
│ │ /login │ /dashboard │ /jobs/[jobId] │ │
│ │ GitHub │ New Job / │ SSE log stream │ │
│ │ OAuth │ History / │ + PR / Preview links │ │
│ │ │ Settings │ │ │
│ └──────────┴────────────┴────────────────────────┘ │
└──────────────────────┬───────────────────────────────┘
│ REST + SSE
▼
┌──────────────────────────────────────────────────────┐
│ NestJS API Server (Backend) │
│ │
│ AuthGuard → AgentController → AgentService │
│ JobsService (Prisma + Neon PostgreSQL) │
│ │
│ ┌────────────────────────────────────────────────┐ │
│ │ Agent Pipeline (per job) │ │
│ │ Groq LLM — tool call planner │ │
│ │ 7 sequential tools in E2B sandbox │ │
│ └────────────────────────────────────────────────┘ │
│ │
│ External Services: │
│ ├── GitHub (Octokit) — clone / branch / PR │
│ ├── E2B — isolated sandbox execution │
│ ├── Lingo.dev SDK + MCP — translation │
│ └── Vercel API — preview deployment │
└──────────────────────────────────────────────────────┘
Why these choices?
| Decision | Rationale |
|---|---|
Monorepo (/client + /server) |
Clear separation; each deploys independently |
| SSE over WebSockets | One-way log streaming is all we need; SSE is simpler and HTTP-native |
| E2B sandboxes | Complete process isolation — git, npm, node all run in throwaway VMs. No untrusted code on our servers |
| Sequential tool forcing | Prevents the LLM from skipping steps or calling tools out of order |
RxJS ReplaySubject per job |
Late-joining SSE connections replay all past events from job start |
| Groq (Llama 3.3 70B) | Fast inference, free tier available, excellent tool-calling accuracy |
The 7-Step Agent Pipeline
This is the core of LingoAgent. Each job runs a strictly sequential 7-step pipeline. The LLM is only ever shown one tool schema at a time (toolChoice: 'required'), forcing it to call that exact tool. The server executes the tool manually — the LLM only plans, never executes.
Step 1: clone_repo
Spins up a fresh E2B cloud sandbox and clones the repository into /workspace. Returns a sandboxId that every subsequent tool uses.
Step 2: detect_framework
Reads package.json, next.config.ts, and directory structure. Identifies Next.js version, App Router vs Pages Router, and the layout file path. If it's not Next.js App Router, the pipeline halts immediately with a clear error — no wasted time.
Step 3: analyze_repo
Scans for existing i18n libraries (next-intl, i18next, react-i18next). If found, the agent stops and warns the user about potential conflicts. Counts JSX files to estimate the translation workload.
Step 4: setup_lingo
Queries the Lingo.dev MCP server for exact setup instructions for the detected framework. Then:
- Writes
i18n.jsonconfiguration - Creates the
TextTranslatorcomponent,LingoProvider, andLanguageSwitcher - Patches
layout.tsxto wrap the app with the provider
This step is where the MCP server shines — instead of the LLM hallucinating configuration, it gets verified, up-to-date instructions directly from Lingo.dev.
Step 5: install_and_translate
The heaviest step:
- Runs
npm installinside the sandbox - Uses Babel AST parsing to extract every hardcoded JSX string — text nodes,
placeholder,title,alt,aria-labelattributes - Translates extracted strings in chunks via the Lingo.dev SDK
- Writes locale JSON files to
public/locales/{lang}.json
If the Lingo.dev API key is invalid or quota is exceeded, the pipeline aborts immediately with actionable guidance pointing the user to the Settings tab.
Step 6: commit_and_push
Uses Octokit to:
- Create a new branch (
lingo/add-multilingual-support) - Commit all modified and new files
- Open a pull request with a structured description
The PR body includes what was translated, which files were modified, and known limitations.
Step 7: trigger_preview
Calls the Vercel API to trigger a preview deployment for the new branch. Polls deployment status every few seconds until it's ready, then returns the live preview URL.
The SSE stream emits { type: 'complete', data: { prUrl, previewUrl } } — and the frontend displays both links.
How the LLM Orchestration Actually Works
This is the part I found most interesting to build. The pattern is LLM as planner, not executor.
// Simplified version of the core loop
const result = await generateText({
model: groq('llama-3.3-70b-versatile'),
system: SYSTEM_PROMPT,
messages: conversationHistory,
tools: { [currentTool.name]: currentTool.schema },
toolChoice: 'required', // Force the LLM to call THIS tool
maxSteps: 1,
});
Key design decisions:
One tool at a time. The LLM never sees all 7 tools simultaneously. It sees exactly one tool schema and is forced to produce arguments for it. This eliminates tool selection errors.
Server-side execution. The LLM returns tool call arguments. The server validates them, executes the tool, and feeds the result back into the conversation history. The LLM never touches the actual APIs.
Up to 3 retries. If the LLM produces invalid arguments, the server appends a correction prompt and retries. After 3 failures, the pipeline aborts.
Context window as state. Each tool's return value feeds the next through the LLM's conversation history. The
sandboxIdfrom step 1 flows through steps 2–7. Theframeworkfrom step 2 informs step 4. ThebranchNamefrom step 6 feeds step 7.
This architecture avoids the two biggest pitfalls of LLM agent systems:
- Hallucination — the LLM can't hallucinate a tool call because it's only shown one option
- SDK timeouts — the server controls execution timing, not the LLM
Real-Time Observability
Users can tolerate a minute process. They cannot tolerate a minute black box.
Every tool emits structured log events through an SSE stream:
// Each tool emits logs via an emit function
emit({ type: 'log', message: '📦 Cloning repository...', status: 'info' });
emit({ type: 'progress', step: 'clone_repo', percent: 15 });
// ... work happens ...
emit({ type: 'log', message: '✅ Repository cloned successfully', status: 'success' });
On the frontend, the /jobs/[jobId] page opens an EventSource connection and renders logs in real time. Because the backend uses an RxJS ReplaySubject, even if the user navigates away and comes back, they see the full log history from the start.
Auth Flow: GitHub Token as Bearer
LingoAgent uses GitHub OAuth exclusively — no passwords, no separate accounts.
The clever part: the same GitHub access token that authenticates the user on the frontend is forwarded as a Bearer token to the NestJS backend. The backend:
- Validates the token exists
- Uses it directly to call GitHub APIs (clone, branch, commit, PR) on behalf of the user
No separate JWT system. No session store on the backend. The user's own GitHub permissions govern what repositories the agent can access.
Custom API Keys: Bypassing Free Tier Limits
One practical challenge: shared API keys hit rate limits fast during a hackathon demo.
LingoAgent lets users supply their own Lingo.dev and Groq API keys via Dashboard → Settings. These keys are:
- Stored in
localStorage(never sent to any third party) - Sent with each job request as optional headers
- Used by the backend to override server-side defaults
This means the demo works even when the shared keys are exhausted — users just paste in their own free-tier keys.
Tech Stack
Frontend
| Tech | Purpose |
|---|---|
| Next.js 14 (App Router) | React framework |
| NextAuth.js | GitHub OAuth |
| Tailwind CSS | Styling |
| TypeScript | Type safety |
Backend
| Tech | Purpose |
|---|---|
| NestJS 11 | API server, DI |
| Vercel AI SDK |
generateText + tool calling |
| Groq (Llama 3.3 70B) | LLM inference |
| E2B | Isolated sandbox VMs |
| Lingo.dev SDK | String translation |
| Lingo.dev MCP | Setup instructions |
| Octokit | GitHub REST API |
| Prisma + Neon PostgreSQL | Job storage |
| RxJS | SSE stream management |
Three Principles That Guided Every Decision
1. Reliability over breadth
A demo that works perfectly for Next.js App Router is worth more than a demo that claims to support five frameworks but breaks on all of them.
Lingo.dev provides its deepest support for Next.js App Router. Its MCP server, compiler, and SDK are all tuned for this stack. Supporting Vite, Remix, or Pages Router would each require separate detection logic, different patching strategies, and independent testing — multiplying the surface area several times.
In a hackathon, doing one thing flawlessly beats doing five things poorly.
2. Observable beats fast
The live log stream isn't just a feature — it's core to the product experience. Watching the agent clone, detect, configure, translate, commit, and deploy in real time builds trust and makes the few minute wait feel productive, not idle.
3. LLM as planner, not executor
The Groq LLM decides what to do (which arguments to pass). The NestJS server decides how to do it (actual API calls, file operations, error handling). This separation prevents hallucination, avoids SDK timeout issues, and makes the pipeline deterministic and debuggable.
Known Limitations (Honest Assessment)
These are deliberate scope constraints, not bugs:
- Next.js App Router only — Pages Router, Vite, Remix not supported
-
Hardcoded strings in JS logic are not translated — Babel AST targets JSX text nodes and common attributes (
placeholder,title,alt,aria-label). Strings in variables or API responses may be missed - Large repos may time out — E2B sandboxes have a configurable timeout (default 10 min)
- No monorepo support — single-app repositories only
-
Existing i18n setups may conflict — repos already using
next-intlori18nextmay produce conflicts
What I Learned
Prisma 7 is a breaking change. The
urlfield inschema.prismais no longer supported — datasource configuration moved toprisma.config.ts. This caused real deployment pain that taught me to always check for major version breaking changes.LLM tool-calling is fragile unless you constrain it. Showing an LLM 7 tools and hoping it calls them in order is a recipe for chaos. Showing it one tool at a time with
toolChoice: 'required'produces deterministic, reliable behavior.SSE is dramatically simpler than WebSockets for one-way streaming. No connection management, no heartbeats, no reconnection protocol — just
text/event-streamand you're done.E2B sandboxes are a superpower for agent systems. Running untrusted code? npm install with unknown dependencies? Git clone with arbitrary URLs? Put it all in a throwaway VM and don't think twice.
The MCP protocol is underrated. Instead of the LLM hallucinating framework configuration, it gets verified instructions from Lingo.dev's own MCP server. This single integration eliminated an entire class of bugs.
Checkout the Video
Demo Video: Google Drive link
Try It Yourself
Demo repo: Kashif-Rezwi/lingo-agent-demo-app — a clean Next.js 14 App Router landing page built for testing.
- Sign into LingoAgent with GitHub
- Paste
https://github.com/Kashif-Rezwi/lingo-agent-demo-app - Select Japanese, French, and Arabic
- Click Start and watch the agent work
- Review the GitHub PR and live Vercel preview
Built by Kashif Rezwi for the Lingo.dev Hackathon 2026.
Top comments (0)