Case study

TaleSelf

A story game for learning languages, where you are the main character.

Role
Solo: product, design, app, API
Year
2026, ongoing
Platform
Android (closed testing), web
Stack
Flutter · TypeScript · Postgres · Gemini
Choosing what happens next: three French choices marked A2, B1 and B2
TaleSelf home: 7-day streak, coins, stars and the next chapter of The Ember Crown
Reading a chapter in French with audio narration and the reader’s character beside the text
The idea

Drills teach words. Stories make you stay.

Most language apps are good at flashcards and bad at the thing that actually makes you fluent: reading a lot of text you care about. Graded readers help, but a book does not react to you, and nobody finishes a book that does not hook them.

TaleSelf puts the learner inside the story. You pick a language and a level, choose a character that looks how you want, and read an adventure where people call you by name. At every step you decide what happens next, and the story bends to that choice. Every passage can be read aloud by a narrator, and any word can be tapped, translated and saved.

The engine

One shared story, written only once.

Generating a fresh chapter for every reader would be slow and expensive. Instead every passage is shared: the first reader to open a branch pays for it, and everyone after reads it instantly.

  1. 1

    A reader picks a choice

    Each step offers three choices, each tagged with its own CEFR level, so a choice can be easy or a stretch.

  2. 2

    Explored before?

    If someone already took this branch, the passage is read from Postgres. No model call, no wait, no cost.

  3. 3

    If not, Gemini writes it once

    The model writes the next passage in English as a template with slots, guided by recent passages for continuity.

  4. 4

    Hydrated for every player

    Slots like {name}, {they} and {their} are filled with each reader’s character, then translated to their language.

Stored once
{name} stands near the tall doors. For an hour {they} have listened to the whispers of the court.
Read by Aryan, in French
Aryan se tient près des hautes portes. Depuis une heure, il écoute les murmures de la cour.
Characters

Drawn in code, not in Photoshop.

A character is a small piece of data: hair style, mustache, glasses and six colors. The app paints it with a Flutter CustomPainter, and the website and admin draw the very same shapes as SVG, in the same 190 by 250 units. Because it is code, the mouth lip-syncs while the narrator talks, the eyes blink and look around, and a new character costs a few bytes instead of an illustrator’s week.

These are the real characters from the app, rendered by the same function. Click one to hear its recorded hello, each in a different language; the mouth follows the voice. Two of them wear the king and queen outfit a league winner gets.

  • Mia
  • Leo
  • Zara
  • Omar
  • Lena
  • Noah
  • Aisha
  • Ravi

The game loop

Built like a game, so people come back.

Learning a language is a habit problem. Every reward flies to its counter, every unlock gets a celebration, and the numbers are tuned so nothing can be bought around the habit itself.

  • Streaks with gifts

    A 5-day streak unlocks a blue character; a chest opens every 10 days. A streak freeze costs 150 coins and you can hold 2.

  • Weekly leagues

    Groups of 30 readers across six tiers, Bronze to Diamond. Top 5 move up, bottom 5 move down, podium gets coins.

    TaleSelf weekly leagues screen
  • A word ladder

    Save any word while reading. Spaced repetition brings it back after 1, 3, 7, 14 and 30 days until you know it.

    TaleSelf a word ladder screen
  • A shop that respects you

    Coins buy characters, refills and freezes. Rewarded ads are optional and keep the game free to play.

    TaleSelf a shop that respects you screen
Under the hood

The stack, end to end.

App

  • Flutter & Dart
  • Provider
  • flutter_animate
  • CustomPainter avatars
  • Local notifications
  • AdMob rewarded ads

API

  • Node.js & TypeScript
  • Fastify + Zod
  • Drizzle ORM
  • PostgreSQL on Neon
  • Vercel

AI

  • Gemini on Vertex AI for stories
  • Gemini translation
  • Gemini text to speech narration

Web & tools

  • Marketing website
  • React admin
  • Vitest
  • Sentry
  • Infisical for secrets

What I learned

  • Cache the expensive thing at the right level. Sharing passages across players turned LLM cost from per reader into per branch.
  • Templates beat regeneration. Writing once in English with slots, then hydrating and translating, keeps 22 languages consistent.
  • Game feel is a feature, not polish. A number that silently changes teaches nothing; a coin that flies to its counter with a sound feels like progress.

Light and dark, nine app languages, right to left included.

The interface ships in English, German, French, Spanish, Italian, Dutch, Turkish, Persian and Arabic, with full RTL layouts.

TaleSelf home screen in dark mode
Back to all work