Finding the Right Words

While honing my French language skills, I realized I had no reliable way to know whether I was internalizing the correct sounds.

Existing tools didn't solve this — so I built one.

Cyllenius Reader transforms any article into a bilingual reading experience — French and English, paragraph by paragraph — with audio to train the ear.

Rhyme & Reason

While built for personal use, I approached the architecture with the same rigor I apply to enterprise consulting. A few of the decisions that reflect that

Server-Side Architecture

All translation happens server-side — every call to Claude and Gmail runs through authenticated serverless functions. Nothing is ever processed in the browser, and the serverless model means the app scales automatically with no infrastructure to maintain.

Performance & Cost Efficiency

Featured articles are bundled directly into the build at compile time. Translated articles are cached locally after processing and flagged so they're never translated twice. No cold-start API dependencies, no repeat calls, no duplicate costs — the experience is instant from the first load.

Built for Forward Compatibility

I chose EPUB as the delivery format with scaling and forward compatibility in mind. It's the open standard across every major e-reader — Kindle, Kobo, Nook, BOOX — and it preserves the bilingual formatting exactly, so the feature works regardless of what device you're reading on.

Security in Layers

Gmail connects via OAuth 2.0 so no credentials are stored in plain text. Every backend request is authenticated with a server-side secret, and the app sits behind an access gate — it's not publicly reachable. The Anthropic API has a spending cap to prevent runaway cost exposure, and the Gmail connection runs through a dedicated inbox completely separate from any personal account. Risk is contained by design.

Under the Hood

Layer 1 — Content Sources
Gmail Inbox
label: to-translate
+
to-translate → processed → translated
  • Forward any article to the Gmail account
  • Apply the to-translate label to queue it
  • After processing, label swaps to translated — never re-processed
Permanent Queue
label: queue
+
queue
  • Always-available featured articles
  • Survive the "Clear all" action
  • Great for curated content you return to often
Demo Mode
paste any text
+
  • Paste any English article for instant translation
  • No Gmail account or labels required
  • Calls translate-text API directly
  • Ideal for portfolio demos
Layer 2 — Auth & Security
Edge Middleware
middleware.ts
+
  • Basic auth gate on all non-API routes
  • Runs at the Vercel edge — before any function executes
  • Credentials stored as Vercel environment variables
Gmail OAuth 2.0
Google API v1
+
  • Refresh-token flow — no user login required at runtime
  • Token refreshed on each serverless function invocation
  • Scoped to Gmail read + label modification only
API Shared Secret
Vercel env variable
+
  • Frontend and backend share the same secret
  • Injected into the frontend bundle at build time via Vercel env
  • Validated on every serverless function call
Layer 3 — Serverless Processing
fetch-and-translate
api/fetch-and-translate.js
+
✦ calls Claude Sonnet 4.6
  • Fetches all emails labeled to-translate
  • Sends each article to Claude paragraph-by-paragraph
  • Forced tool_choice + XML delimiters for reliable pairing
  • Swaps label to translated on success
  • 300s Vercel Pro timeout for long articles
fetch-queue
api/fetch-queue.js
+
  • Returns all emails labeled queue
  • Queue articles are never deleted or re-labeled
  • Merged with localStorage cache on the frontend
translate-text
api/translate-text.js
+
✦ calls Claude Sonnet 4.6
  • Translates a single pasted text (Demo Mode)
  • Same Claude pipeline — tool_choice + XML pairing
  • No Gmail interaction — self-contained
send-to-kindle
api/send-to-kindle.js
+
  • Exports a translated article to Kindle
  • Formats bilingual content for e-reader display
Layer 4 — Reader Interface
Home Queue
pages/Home.tsx
+
  • Lists all available translated articles
  • Merges Gmail fetch results with localStorage cache
  • Permanent queue articles always appear at the top
Bilingual Reader
pages/Reader.tsx
+
  • French paragraph above, English below — per ParagraphPair
  • Left accent bar visually ties each FR/EN pair together
  • Immersive parallel reading for language acquisition
Text-to-Speech
Web Speech API
+
  • Click speaker icon or French text to hear pronunciation
  • Per-paragraph speed controls: 0.5×, 0.8×, 1.0×
  • French locale voice — native accent playback
Persistent Cache
utils/storage.ts
+
  • Articles stored in localStorage after first fetch
  • Survive page refresh — no redundant API calls
  • "Clear all" removes non-queue articles only
Deployed on Vercel
React frontend + 4 serverless functions · 300s max function duration
Edge middleware runs globally
before any route resolves