EchoLingo is a mobile-first translation app built for live demos. It supports guest translation, two-speaker conversation mode, Google sign-in, saved history, and optional custom voice playback powered by ElevenLabs.
This is the fastest happy-path demo and does not require login.
What to do:
- Open
https://echo-lingo-eight.vercel.app/. - Pick a source language and a target language.
- Type a phrase in the left panel.
- Press the send button to translate it.
- Optionally play the original text or translated text with the audio buttons.
What happens under the hood:
- The home page calls
POST /api/translate. src/lib/services/openai.tssends the text to OpenAIgpt-4o-mini.- Audio playback uses
POST /api/synthesize. src/lib/services/elevenlabs.tsconverts text to speech with ElevenLabseleven_multilingual_v2.
Important behavior:
- The home page is text-first. It does not record microphone audio.
- Guests can use the default AI voice.
- Signed-in users can also select saved voice profiles for playback when a profile includes an ElevenLabs voice ID.
This is the real-time two-speaker mode. It works in guest mode and signed-in mode.
What to do:
- Open
/conversations. - Set each speaker's language.
- Use either speaker mic to record a turn, or type a turn into the shared input area.
- Let the app return the translated reply and play translated audio.
What happens under the hood:
- The UI first tries to create a saved conversation through
POST /api/conversations. - If that request returns
401, the page automatically falls back to guest mode. - Each turn is sent to
POST /api/process-turn. - Audio turns are transcribed with OpenAI
whisper-1. - The transcript is trimmed, translated with OpenAI
gpt-4o-mini, and then sent to ElevenLabs for speech synthesis. - If audio synthesis fails, the translated text still returns and the conversation continues.
Important behavior:
- Both speakers can use mic input or typed input.
- The translated audio auto-plays when synthesis succeeds.
- Logged-in users get persistent history.
- Guests still get the full live translation flow, just without saved conversations.
Google sign-in unlocks persistence and custom voice management.
What to do:
- Open
/loginand sign in with Google. - Use
/dashboardfor a quick overview. - Open
/historyto review saved conversations. - Open
/voices/newto create a voice profile from a recorded sample. - Use that profile on
/or/conversationswhen it has anelevenLabsVoiceId.
What happens under the hood:
- Auth is handled with Auth.js / NextAuth v5 and the Prisma adapter.
- Voice profile creation posts recorded audio to
POST /api/speaker-profiles. - The server calls ElevenLabs Instant Voice Cloning and stores the returned
elevenLabsVoiceId. - Deleting a profile also attempts to delete the linked ElevenLabs voice.
Recommended judge flow:
- Start on
/for a fast guest translation demo. - Move to
/conversationsfor the live dual-speaker experience. - Sign in to show persistence on
/history. - If time allows, show
/voices/newto demonstrate custom voice setup.
- Typed translation on
/ - Text-to-speech playback for source and translated text on
/ - Dual-speaker conversation flow on
/conversations - Microphone capture with
MediaRecorder - Typed fallback input inside conversation mode
- OpenAI transcription for conversation audio turns
- OpenAI translation for both quick translate and conversation flows
- ElevenLabs speech synthesis for playback
- Google sign-in
- Saved conversations and history for authenticated users
- Speaker profile creation and deletion for authenticated users
- Next.js 14 App Router
- React 18
- TypeScript
- Tailwind CSS
- Prisma 7
- PostgreSQL
- Auth.js / NextAuth v5 beta
- OpenAI API
- ElevenLabs API
- Zod
| Route | Purpose | Auth |
|---|---|---|
/ |
Quick typed translation and playback | Optional |
/conversations |
Main dual-speaker live conversation experience | Optional |
/history |
Saved conversations list | Required |
/conversations/[id] |
Saved conversation detail | Required |
/dashboard |
Signed-in overview | Required |
/voices |
Speaker profile management | Required |
/voices/new |
Voice profile creation | Required |
/login |
Google sign-in screen | Guest |
| Endpoint | Purpose | Notes |
|---|---|---|
POST /api/translate |
Quick text translation | Uses OpenAI gpt-4o-mini |
POST /api/synthesize |
Text-to-speech playback | Uses ElevenLabs eleven_multilingual_v2 |
POST /api/process-turn |
Conversation turn pipeline | Handles audio or typed turns, plus optional persistence |
GET /api/conversations |
List saved conversations | Auth required |
POST /api/conversations |
Create a conversation shell | Auth required |
GET /api/conversations/[id] |
Load one saved conversation with turns | Auth required |
GET /api/speaker-profiles |
List voice profiles | Auth required |
POST /api/speaker-profiles |
Create a voice profile | Auth required |
DELETE /api/speaker-profiles/[id] |
Delete a voice profile | Auth required |
POST /api/transcribe |
Placeholder route | Present in repo but not used by the main UI |
The Prisma schema centers on four product entities:
User: authenticated account record used by Auth.jsConversation: a saved conversation container owned by a userConversationTurn: each translated turn inside a saved conversationSpeakerProfile: a saved voice profile, optionally linked to an ElevenLabs voice ID
- Node.js
20.20.2from.nvmrc - PostgreSQL
- An OpenAI API key
- An ElevenLabs API key
- Google OAuth credentials if you want to test login and saved features
nvm use
npm installThis repo does not currently include a committed .env.example, so create .env manually:
DATABASE_URL="postgresql://USER:PASSWORD@HOST:5432/DB_NAME"
OPENAI_API_KEY="your_openai_key"
ELEVENLABS_API_KEY="your_elevenlabs_key"
AUTH_SECRET="your_auth_secret"
AUTH_GOOGLE_ID="your_google_client_id"
AUTH_GOOGLE_SECRET="your_google_client_secret"
AUTH_URL="http://localhost:3000"
NEXT_PUBLIC_APP_URL="http://localhost:3000"
ELEVENLABS_VOICE_ID=""Environment notes:
DATABASE_URL,OPENAI_API_KEY, andELEVENLABS_API_KEYare required for the core translation demo.AUTH_SECRET,AUTH_GOOGLE_ID, andAUTH_GOOGLE_SECRETare required for Google sign-in, dashboard, history, and voice profile features.ELEVENLABS_VOICE_IDis optional. If it is not set, the app falls back to a hard-coded default ElevenLabs voice ID.AUTH_URLandNEXT_PUBLIC_APP_URLare useful local and deployment settings. The current app source does not readNEXT_PUBLIC_APP_URLdirectly.
Use db push here because the repo currently does not include committed Prisma migration files.
npx prisma generate
npx prisma db push
npm run devOpen http://localhost:3000.
Create a Google OAuth client and add:
- Authorized JavaScript origin:
http://localhost:3000 - Authorized redirect URI:
http://localhost:3000/api/auth/callback/google
If you deploy the app, add the production app URL and callback URL too.
If Google shows Error 403: org_internal, switch the consent screen to external or add your account as a test user.
npm run dev
npm run build
npm run start
npm run lintThese checks were validated against the current repo state:
npm run buildpassesnpm run lintpasses with non-blocking Next.js warningsnpx tsc --noEmitpasses after a build has generated.next/types
Current lint warnings:
src/app/layout.tsx: custom Google font stylesheet warningsrc/components/layout/UserProfile.tsx:<img>warning from Next.js lint rules
- The main conversation experience is
/conversations. /conversations/newis an older alternate screen still present in the repo, but it is not the primary demo route.POST /api/transcribeexists as a placeholder and is not part of the primary user flow.- PWA assets such as
public/manifest.jsonandpublic/sw.jsexist, but the registration component is not mounted insrc/app/layout.tsx, so PWA behavior should be treated as incomplete.
src/app/
page.tsx quick translate UI
conversations/page.tsx main conversation UI
voices/page.tsx voice profile manager
history/page.tsx saved conversation history
api/ app router API endpoints
src/components/
AudioRecorder.tsx microphone capture
conversation/ConversationPage.tsx
src/lib/
auth.ts Auth.js config
prisma.ts Prisma client bootstrap
services/openai.ts translation + transcription
services/elevenlabs.ts TTS + voice cloning
services/process-turn.ts conversation pipeline