The Problem: 5 Apps Where 1 Should Exist
My grandmother recited Hanuman Chalisa every morning. When I wanted that same experience on my phone, I downloaded 5 different apps — one for Chalisa, one for Gita, one for Aarti, one for Ramayan audio. Each was ad-heavy, single-purpose, and 40-80MB.
The engineering challenge: build one app that replaces all of them — with 5 languages (Hindi, English, Sanskrit, Tamil, Telugu), offline text, streamed audio, and a ~15MB APK. No backend. No login. Privacy-first.
This post covers the architecture decisions, content strategy, and i18n approach I used to ship SanatanApp to the [Google Play Store](https://play.google.com/store/apps/details?id=com.sanatandevotional.app).
Content Architecture: JSON Over APIs
The first decision was how to store sacred texts. Options:
- Remote API — Flexible but requires internet, adds latency, needs a backend
- SQLite — Overkill for read-only text, adds query overhead
- Bundled JSON — Zero latency, works offline, trivially simple
I went with bundled JSON. The entire Hanuman Chalisa (40 verses × 5 languages) is ~12KB. Bhagavad Gita (700 verses × 5 languages) is ~180KB. All Aartis combined: ~25KB. Total text payload: under 250KB.
The key insight: separate content i18n from UI i18n. Verse translations are embedded in each JSON file (one object per verse with all language fields). UI strings (buttons, labels, navigation) use react-i18next. This avoids the complexity of loading separate translation files for content.
i18n Strategy: react-i18next for UI, Inline for Content
Most i18n tutorials assume all translated content goes through the i18n system. For SanatanApp, that would mean 700 Gita verses × 5 languages = 3,500 translation keys just for one scripture. Unmanageable.
Instead, each verse object carries its own translations:
The VerseBlock component just reads the current language from context and renders the right field:
react-i18next handles only UI strings — about 80 keys total. This keeps the i18n system fast and the content pipeline simple. Adding a new language means adding one field to each verse JSON, not touching any code.
Audio Streaming with expo-av
Text is bundled. Audio is streamed. The Ramcharitmanas full katha is hours of audio — bundling it would make the APK 500MB+. Instead, I map content IDs to public domain streaming URLs in a single audio-sources.json:
The AudioContext provider manages global playback state:
Key decisions: - No download/offline audio in v1 — keeps APK small, avoids storage management complexity - Background playback enabled — users want to listen during commute or cooking - MiniPlayer pinned to bottom — persistent audio bar across all screens, similar to Spotify - Archive.org as primary source — public domain, no licensing issues, reliable CDN
Local State: expo-sqlite for Progress & Streaks
No backend means all user state lives on-device. I use expo-sqlite for three things:
- Bookmarks/Favorites — Save any verse or aarti
- Reading progress — Remember where you left off in each scripture
- Daily sadhana streaks — Track consecutive days of practice
The streak calculation is a simple SQL query that counts consecutive dates backward from today. No cloud sync, no accounts, no privacy concerns.
APK Size: How I Hit ~15MB
Most competing apps are 50-85MB. SanatanApp ships at ~15MB. Here's how:
| Component | Size | Strategy | |-----------|------|----------| | Text content (all scriptures) | ~250KB | Bundled JSON, no images | | App code (JS bundle) | ~2MB | Tree-shaking, no heavy UI libs | | Fonts (Noto Sans Devanagari) | ~1.5MB | Single weight, subset | | Expo runtime | ~10MB | Managed workflow, minimal plugins | | Audio | 0 | Streamed, not bundled | | Images | ~500KB | Minimal — icons only, no hero images |
Key trade-offs: - No images for scriptures — text-only with beautiful typography is actually more readable - Single font weight — Regular only, no Bold/Italic variants of Devanagari font - Minimal dependencies — React Navigation, expo-av, expo-sqlite, react-i18next. That's it. - No analytics SDK — saves ~2MB and aligns with privacy-first positioning
What I Would Do Differently
Ship faster. I spent too long on the design spec before writing code. The verse reader screen alone went through 4 design iterations. In hindsight, the first version was 80% right.
Start with one scripture. Bundling everything (Chalisa + Gita + Aartis + Ramayan) for v1 was ambitious. Launching with just Hanuman Chalisa would have been a faster path to user feedback.
Consider Expo Router over React Navigation. Expo Router (file-based routing) is more idiomatic in the Expo ecosystem now. I went with React Navigation because I knew it well, but Expo Router would have simplified the navigation setup.
The app is live on [Google Play](https://play.google.com/store/apps/details?id=com.sanatandevotional.app). If you're building a content-heavy React Native app with offline-first requirements, the JSON + streaming architecture works well. The total cost of running SanatanApp is $0/month — no servers, no databases, no CDN bills.