Paying in birr
A creator's income lives or dies at checkout, so it had to work with the ways people here actually pay. Tigu takes Telebirr, CBE Birr, M-Pesa and cards through Chapa, an Ethiopian payment gateway, and everything is priced in birr.
On the phone the payment page opens inside the app. When it hands back, the app doesn't take the page's word for it: it asks our server every few seconds until the payment is confirmed, for up to a minute. A decline ends the wait at once, and trying again keeps the plan you picked. Leaving checkout halfway is recorded too, because that was the biggest silent drop between “Join” and a paying member.
Not everyone has a mobile wallet, so there is a second way in: a plain bank transfer. You upload the receipt and its reference number, the creator approves it, and the community or course unlocks as if you had paid online.
One web bug is worth telling. The sign-in cookie was set to be as strict as possible, which meant buyers came back from the payment page signed out. The site now notices that exact return and reloads once, with a guard so it can never loop.
Three languages, everywhere
Every screen, message and notification comes in English, Amharic and Afaan Oromoo. In the app that's a typed dictionary of more than 1,250 strings per language. Anything not yet translated falls back to English instead of showing a blank, and switching language also changes the language of the push notifications the server sends you.
The website has close to 4,000 strings per language. A check runs on every change and fails if any language is missing a string, has an empty one, or gets a placeholder wrong, so a half-translated page can't ship.
Amharic is written in Ge'ez script, and the Latin fonts we use have none of its letters. On the web it gets a matching Ethiopic font of its own. In the app I cut the Latin font down to only the letters it can draw, which took each weight from about 400 KB to 52 KB.

Built for the phones people have
Tigu has to work on budget Android phones, on networks that come and go. A few fixes came straight from that.
The app used to ask the phone whether it was online before every request, and the phone answers by pinging a Google server, which is slow or blocked on many networks here. One wrong “offline” paused every request in the app at once. Now requests just go out, cached answers show right away, and nothing waits on that check.
Google Play marked some budget phones, like Samsung's Galaxy A01, as “incompatible” because the app listed hardware they lack. Marking those features as optional brought them back. The icon library was quietly adding about 2.2 MB to the app, since every one of its 6,131 icons was bundled; a build step now pulls in only the icons we use. Long screens became lists that draw only what's on screen, and the chat thread stopped re-drawing itself thirty times a second as you scrolled.
Updates arrive over the air in the background and never hold up opening the app. When a real store update is needed, Android shows Google Play's own update sheet.
Live classes, third time lucky
Live classes went through three versions. They began on Jitsi, moved to LiveKit with our own room screen (hand raising, reactions and all), and now hand off to YouTube Live and Google Meet.
Meet couldn't be shown inside the app, because Google blocks its sign-in in embedded browsers, so it opens in the Meet app instead. YouTube plays in the app, with the scrubber hidden for streams. Letting LiveKit go took a 950-line screen and all of its native video code out of the app.
Tested like it's on a slow phone in Addis
The website has the deepest tests. Its browser tests run in Chrome, Firefox and Safari's engine and on Pixel and iPhone screen sizes, all set to Addis Ababa's time zone, against a fake server so the results don't depend on the network.
Each of its 446 features has an ID, and a check fails if any feature has no test. In October all 1,286 tests passed. Every night a job runs every browser and checks the test server's API against what the site expects, and opens an issue when they drift. Speed is measured on a simulated slow 4G phone, with a budget for each page.
What I'd change next
The app has no automated tests yet. The website shows how much that catches, so the app's main flows (sign in, join, pay, watch) are next.
The website is over its own JavaScript budget: the landing page ships about 295 KB against a goal of 120 KB. The check flags it on every change, which is the point; now it needs the work.
Made with
- The app
- Expo, React Native, Expo Router, TanStack Query, Zustand, NativeWind, socket.io, EAS Update, Sentry
- The website
- Next.js, React 19, next-intl, TanStack Query, Tailwind CSS, hls.js, Playwright, Vitest



