EdgeLang is a self-study language learning application that leverages AI to create personalized learning content from daily diaries and topic-based requests.
Write about your day in your native language, and EdgeLang will:
- Generate a Mini-Story: A children's book-style story in your target language, simplified to your current level.
- Visuals & Audio: Generate mnemonic-cued images and high-quality TTS audio for every page.
- Extract Vocabulary: Automatically identify key words from the story and add them to your VoDex.
The VoDex is your personal vocabulary index. Words are organized into Packages:
- Diary Packages: Automatically created from each processed diary entry.
- Topic Packages: Generate a pack of 20-30 words by simply describing a topic (e.g., "At the airport", "Calisthenics vocabulary").
- Mnemonic Images: Every word gets an AI-generated image with mnemonic cues (Fire-themed for Masculine, Ice-themed for Feminine).
Read your generated stories with a "page-turn" effect, synchronized audio, and click-to-translate functionality.
- Frontend: Next.js 15 (App Router), React 19, Tailwind CSS 4, shadcn/ui.
- Backend: tRPC 11 (with Subscriptions for live progress).
- Database: PostgreSQL with Drizzle ORM.
- Auth: better-auth 1.4.
- Storage: S3-compatible (MinIO for local dev, Railway S3 for prod).
- AI Engine:
- Text: OpenRouter (
minimax/minimax-m2.1) for structured JSON output. - Images: fal.ai (
fal-ai/z-image/turbo). - Audio: ElevenLabs (Multilingual v2).
- Text: OpenRouter (
Copy .env.example to .env and fill in the following values:
# Database
DATABASE_URL="postgresql://languagebuddy:languagebuddy@localhost:5432/languagebuddy"
# BetterAuth
BETTER_AUTH_SECRET="your_secret_here"
BETTER_AUTH_URL="http://localhost:3000"
GOOGLE_CLIENT_ID="your_google_id"
GOOGLE_CLIENT_SECRET="your_google_secret"
# Storage (Local MinIO)
S3_ACCESS_KEY_ID="minioadmin"
S3_SECRET_ACCESS_KEY="minioadmin"
S3_ENDPOINT="http://localhost:9000"
S3_BUCKET_NAME="languagebuddy"
S3_REGION="us-east-1"
# AI Services
OPENROUTER_API_KEY="your_openrouter_key"
ELEVENLABS_API_KEY="your_elevenlabs_key"
FAL_KEY="your_fal_key"
# Email (Resend)
RESEND_API_KEY="re_xxxxx"openssl req -new -x509 -days 365 -nodes \
-out server.crt -keyout server.key \
-subj "/CN=localhost"
chmod 600 server.key
chmod 644 server.crtUse Docker to run the required services:
PostgreSQL:
docker run --name languagebuddy-postgres \
-e POSTGRES_USER=languagebuddy \
-e POSTGRES_PASSWORD=languagebuddy \
-e POSTGRES_DB=languagebuddy \
-p 5432:5432 \
-v $(pwd):/var/lib/postgresql/ssl:ro \
-d postgres:16 \
-c ssl=on \
-c ssl_cert_file=/var/lib/postgresql/ssl/server.crt \
-c ssl_key_file=/var/lib/postgresql/ssl/server.keyMinIO:
docker run -d \
--name language-buddy-s3 \
-p 9000:9000 \
-p 9001:9001 \
-e "MINIO_ROOT_USER=minioadmin" \
-e "MINIO_ROOT_PASSWORD=minioadmin" \
-v ~/minio/data:/data \
quay.io/minio/minio server /data --console-address ":9001"Run the following to set up your schema:
pnpm db:generate
pnpm db:pushGenerating AI content (stories, images, audio) is time-consuming. We use a background processing pattern:
- Client triggers a mutation (e.g.,
createPackageFromTopic). - Server creates a record with
status: "processing"and returns immediately. - A background task runs the AI pipeline, updating
processedWordsandstatusin the DB. - The client uses a tRPC Subscription (
packageProgress) to listen for real-time updates via anEventEmitter.
NEVER expose direct S3 URLs. The application uses presigned URLs for all asset access.
- Use
getReadPresignedUrl(key)for displaying images/audio. - Assets are organized by type:
stories/{id}/...andvocab/{id}/....
Prompts are centrally managed in src/lib/server/ai/prompts.ts. We enforce Structured Output using Zod schemas to ensure the LLM returns valid JSON that matches our database structure.
EdgeLang uses a waitlist system for beta access control. New users who sign up are placed on a waitlist (their account is created with banned: true) and see a beta message until an admin approves them.
- Waitlisted (
banned: true): User sees beta message, cannot access features - Approved (
banned: false,role: 'user'): Full access to all features - Admin (
role: 'admin'): Full access plus admin dashboard at/admin
Admins can access the admin dashboard at /admin to:
- View all users and their status
- Approve waitlisted users (sends approval email)
- Move approved users back to waitlist
- Promote users to admin
- View active sessions
- Send beta invitations via email
To create the first admin user in a production environment:
- Sign up normally through the application at
/auth/signup - Connect to your production database
- Run the following SQL command:
UPDATE "user"
SET role = 'admin', banned = false
WHERE email = 'your-email@example.com';- Refresh the page - you now have admin access and can see the Admin link in the sidebar
- Sign up normally through the application
- Run
pnpm db:studioto open Drizzle Studio - Navigate to the
usertable - Find your user by email
- Update
roleto'admin'andbannedtofalse - Save changes
Once you have admin access, you can promote other users to admin through the Admin Dashboard:
- Go to
/admin - Find the user in the Users tab
- Click "Make Admin" button
- Confirm the action
- Admin users bypass the waitlist check automatically
- Only admins can access the
/adminroute - Admins cannot demote themselves
- Admins cannot ban other admin users
EdgeLang uses Resend for sending emails. Add your API key to .env:
RESEND_API_KEY="re_xxxxx"- Invitation Email: Sent when an admin invites a new user
- Approval Email: Sent when an admin approves a waitlisted user
Email templates are located in src/lib/server/email.ts. Update the from address to match your verified domain in Resend.