Indic Reader
🌐 Live app: https://indic-reader.pages.dev/
A single-file web app for reading, narrating, and translating Indic-script scriptures (Sanskrit, Hindi, Gujarati). Works in any modern browser. Everything runs locally on your device by default — no server, no tracking, no cookies. Cloud-based features (OCR, translation, dictionary) are optional and opt-in with explicit consent.
Table of contents
- Quick start
- Loading documents
- Reading & narration
- Full-screen immersive reading
- Layouts (A / B / C)
- Language filter
- Outline / table of contents
- Word meanings & translation
- Split Sandhi (word separation)
- Correcting language & text
- Vedabase deep links
- OCR for scanned documents
- Re-OCR a region
- Cloud OCR setup
- Google Drive integration
- Settings & preferences
- Keyboard shortcuts
- Privacy & data handling
- Troubleshooting
- Known limitations
Quick start
- Open
index.htmlin any modern browser (Chrome, Firefox, Safari, Edge — desktop or mobile) - Drag a file onto the drop zone, or tap 📁 Choose Files
- For PDFs, select page range, tap ▶ Process Pages
- Use the ▶ Play button to narrate, or scroll/tap to read
For scanned PDFs (image-only), turn on Force OCR before processing.
Loading documents
Five ways to load content:
| Method | When to use |
|---|---|
| 📁 Choose Files | Pick files from your device — most common path |
| 📂 Browse | Open a folder on desktop (File System Access API); falls back to multi-file picker on mobile |
| 🟢 Drive | Pick from Google Drive (requires one-time OAuth setup — see Drive integration) |
| ✍️ Paste Text | Paste Sanskrit/Hindi/Gujarati text directly |
| Drag & drop | Drop files anywhere in the page |
| Drive share link | Paste a public Drive URL — file must be set to "Anyone with the link can view" |
Supported file types: PDF, DOCX, TXT, RTF, HTML, XML, images (JPG, PNG, etc.)
Pasting formatted text
When you paste from a rich source (a web page, a document editor), the app reads the clipboard's HTML so paragraphs, line breaks, and bullet lists are preserved rather than flattened — bullets are normalized and each paragraph keeps its own line. A small indicator tells you whether rich formatting was detected. If a source only provides plain text (some apps strip formatting on copy), a ¶ Restore paragraphs button reconstructs structure by breaking at sentence ends and before section numbers, and recognizes a wide range of bullet markers. Pasted text starts a clean document, so it never disturbs a PDF you already have open (its reading position and OCR cache are preserved). Structure carries through to both Text View and Document View.
Reading & narration
Two views, switchable via the tabs at the top of the reader:
- 📝 Text View — extracted text reflowed for screen reading. One line/verse per row. Each row has a ★ favorite button.
- 📄 Document View — original page rendered as image with text overlay highlighting. Shows the page exactly as printed.
Player controls (bottom bar)
| Button | Action |
|---|---|
| ⏮ | Previous line / verse |
| ▶ / ⏸ | Play / pause narration |
| ⏹ | Stop narration |
| ⏭ | Next line / verse |
| Page chip | Tap to jump to a specific page |
Narration chips (floating player)
Reading-time options sit on the floating player as compact chips that highlight when active (tap to toggle), kept on a single line:
- 🕉 Chant — slow, contemplative narration speed (overrides per-language speed)
- Scroll (Auto-scroll) — scrolls the page automatically as narration progresses, in both Text and Document view
- Pause-skip (Pause at skip) — when narration encounters a line in a language you've filtered out, pause briefly so you can decide whether to skip or include it
- 📖 Cont. (Continuous) — auto-advance through pages without manual ⏭. In Continuous mode the app OCRs the next page just-in-time: a few lines before the current page ends it prepares the next page in the background, then narration flows straight into it — you no longer have to pre-select every page. Each page prepared this way is written to the OCR cache, so it's instant (and free of repeat cloud cost) next time.
⚙️ Settings panel
Set-and-forget options live in a single ⚙️ Settings panel (button beside Library), grouped into OCR, Playback, Reading layout, and Document, so the reading surface stays uncluttered:
- 📞 Pause on call — auto-pause narration on a phone call or when the tab is hidden, resume after
- 📍 Resume — remember your position (page, line, word) and offer to continue; reopening a multi-page PDF via Continue auto-selects and processes just the page you were on, then jumps to the exact line/word
- 🗄 OCR cache — cache recognized text + word boxes per page so reopening/re-reading is instant and avoids repeat cloud-OCR cost (turning it off clears the cache)
- ⚠️ Check OCR — underline words the OCR engine was unsure about (low confidence) so they're easy to spot and fix
- 🔋 Screen-off play — keep narrating when the screen turns off or the app is backgrounded, where the device allows it (off by default; see Background & car playback below for the trade-off)
- 📄 Doc view on play — switch to Document View automatically when narration starts
- 🔊 Read translation in narration — also speak each line's translation during continuous narration
- Force OCR, ☁️ Cloud OCR, ↩ Fallback to local, ⚙ Cloud setup — OCR engine configuration
- Reading layout A / B / C — the layout selector
- 📋 Copy Filtered — copy only the lines matching the current language filter
Full-screen immersive reading
A ⛶ Full screen tab puts the reader into a distraction-free mode showing only the current view (Text or Document) with a single floating control bar — minimise ⤢, ⏮, play/pause, ⏭, a Text/Document toggle, and a language filter. The app header, upload area, settings, and all editing tools are hidden. Document View fills the entire screen in this mode (the normal half-height cap is removed). The control bar stays visible while you read, and the app enters full-screen automatically once a page is processed, so you go straight into reading. Exit any time with ⤢. On Android Chrome this also engages the browser's true full-screen; on iOS it relies on the immersive layout (and is fully chrome-free when the app is added to the Home Screen).
Per-verse translation narration
Each translated verse block carries its own 🔊 button next to the translation text — tap it to hear that verse's translation read aloud in the right voice for the target language, independent of the main narration.
Background & car playback
While narrating, the screen is held awake so playback doesn't stop when the display would otherwise sleep. With a phone connected to a car by Bluetooth, narration plays through the car speakers and the basic steering-wheel/head-unit play/pause may control it. Turning on 🔋 Screen-off play keeps a silent media session alive so lock-screen transport controls appear and playback continues when backgrounded — but because that media session competes with the speech channel, it can make narration less stable on Android Auto; leave it off for the most reliable in-car audio. A web app cannot appear as a native app on the Android Auto / CarPlay projected screen — that requires a native build — so use Bluetooth audio mode in the car.
Line numbers
Text View shows a muted line number in the left gutter of each line, to make it easy to reference a line for correction. The numbers are drawn so they're never included when you select or copy text.
Speed chips (per language)
Narration speed is set per language (Gujarati, Hindi, Sanskrit) with three compact speed chips showing the current value (e.g. ગુ 1.0×). Tap a chip to expand a fine-tune slider for that language; tap again to collapse. Sanskrit chanting is traditionally slower; Hindi/Gujarati prose can be faster. The default is 1.0× for all three (existing saved preferences are kept). The Sanskrit chip drives both plain reading speed and chant-mode pacing across its full range.
Screen wake lock
While narrating, the screen stays on automatically. When you stop narration or close the tab, it releases. Requires HTTPS; some browsers (older Safari) don't support it and screen behavior falls back to OS default.
Chhand (verse metre) & scansion
Each Sanskrit verse shows a metre pill, e.g. 📐 Śārdūlavikrīḍita · 38 akṣ. Metre is identified two ways:
- By guru/laghu scansion (rigorous): the app marks each syllable heavy (guru, –) or light (laghu, ‿) using the actual rules — long vowel, anusvāra/visarga, or a following consonant cluster make a syllable heavy — then matches the heavy/light pattern against known metres (Indravajrā, Upendravajrā, Vaṃśastha, Vasantatilakā, Mālinī, Śārdūlavikrīḍita, Sragdharā). When the pattern matches, the exact metre name is shown with no "approx." hedge.
- By syllable count (fallback): if no pattern matches, it falls back to count-based families — Anuṣṭubh (32), Gāyatrī (24) shown reliably; Triṣṭubh/Jagatī/long shown as "(approx.)".
A verse has two related, separate controls:
- 📐 Scan — opens a syllable-by-syllable grid of the whole verse with – guru (heavy) / ‿ laghu (light) above each akṣara. This is the dense, rigorous view of the metre.
- 🎵 Chant tones — shows each pāda on its own row, labelled with its recitation tone (▲ high / ◆ mid / ▼ low), with the pāda kept as readable whole words and the – / ‿ marks sitting on a small line above each word. This is the reading-friendly view: it keeps the original chant-mode text (words are never broken into a per-akṣara grid) and simply adds the scansion marks on top. Each pāda has a 🔊 to hear it, and 🎵 Chant whole verse plays them in sequence.
The tones are a recitation aid (not canonical Vedic svara — laukika verses carry no textual accent); the guru/laghu marks are the rigorous scansion. Both views share the same scansion engine, so the marks always agree.
Syllables display with their full conjunct spelling: a consonant cluster that opens a word (e.g. the स् of स्वर्ण, the क् of क्षेत्रेषु) is shown attached to the syllable it begins (स्वर्, क्षेत्), while a cluster in the middle of a word closes its syllable (वर्). This is a display refinement only — the heavy/light weights and metre detection are unchanged.
Honest limit: scansion is only as accurate as the text. OCR errors (a wrong vowel length, a dropped conjunct) shift the marks, so verify against a clean source for serious study. The metre also shapes chant-mode recitation pacing.
Layouts (A / B / C)
Top of the reader has a layout selector. Choose what suits your reading style:
- A — Single column (default): on-demand translation. Tap "Translate verse" on each line if you want it.
- B — Side-by-side: Sanskrit on the left, translations on the right. Auto-translates as lines scroll into view. Desktop-only — falls back to C below 768px width.
- C — Stacked pairs: each Sanskrit verse followed immediately by its Gujarati + Hindi + English translation. Auto-translates everything. Best for mobile.
Choice is saved in your browser; persists across sessions.
Switching to B or C the first time will prompt for consent (translation sends text to Google Translate's public endpoint).
Language filter
Buttons above the text: All / ગુ Guj / સં Sans / હિ Hin
- Filters which lines are visible AND which lines are narrated
- Useful when reading scripture with both Sanskrit verses and Gujarati commentary — you can hear just the Sanskrit, or just the commentary
Outline / table of contents
For DOCX files, the 📑 Outline button appears in the reader header. Tap it to see the document's structure as a navigable list.
The outline shows different things depending on the document:
| Strategy | When used | What you see |
|---|---|---|
| Headings | DOCX uses Word's Heading 1/2/3 styles | Hierarchical TOC indented by heading level |
| Author page breaks | DOCX has explicit page breaks (Ctrl+Enter in Word) | "Page 1, Page 2, …" matching author intent |
| Auto-chunked | Document has neither headings nor page breaks | "Chunk 1, Chunk 2, …" — each ≈500 words |
Tap any entry to scroll the reader to that section. Brief highlight pulse shows where you landed.
Word meanings & translation
Show Meanings (Sanskrit only)
Below each Sanskrit verse, tap Show Meanings to look up each word:
- Queries the Cologne University Monier-Williams dictionary (the authoritative Sanskrit lexicon)
- Returns English definitions, then auto-translates them to Gujarati
- Cached per-session — repeated lookups are instant
Source badge per word:
- MW (green) — from Monier-Williams (authoritative)
- GT (grey) — Google Translate fallback (less reliable for technical Sanskrit terms)
Translate verse
Below each line, tap 🌐 Translate verse to get full-verse translations:
- Gujarati, Hindi, and English translations all shown
- Each has a 🔊 button to hear it spoken in that language's voice
- Cached per source — same verse won't be translated twice
This sends the verse text to Google Translate's public endpoint. Disabled by default; first use prompts for consent.
Translate page (Gujarati ↔ Hindi)
In Text View, three buttons in the controls row translate the current page, always leaving Sanskrit verses untouched:
- 🌐 ગુ→हि — translate the page's Gujarati lines into Hindi
- 🌐 हि→ગુ — translate the page's Hindi lines into Gujarati
- 🌐 →EN — translate the page's Gujarati and Hindi lines into English
The translation appears as a block under each translated line (the original stays, so narration and study are unaffected). Each block has its own 🔊 button to read that verse's translation aloud on demand. The translate controls appear only in Text View. Tap the same translate button again to hide. Sanskrit lines are never sent. Results are cached on-device. Uses Google Translate's public endpoint under the same consent as Translate verse. To have translations spoken automatically as part of continuous narration, enable 🔊 Read translation in narration in Settings.
Bookmark notes
Tap the ☆ on any line to bookmark it. In the Library, each bookmark shows the full line text (not just a snippet), and you can attach a note to it (tap "+ Add note"). Notes and the full text are stored on-device.
Split Sandhi (word separation)
Sanskrit verses fuse words together through sandhi (e.g. तपोवनम् = तपस् + वनम्). The ✂️ Split Sandhi button (on every Sanskrit verse, next to Show Meanings) separates them.
How it works — accuracy first, with honest fallback:
- Real morphology — it first sends the verse text to the University of Hyderabad Heritage segmenter, an academic Sanskrit morphological analyzer, routed through your own Cloudflare Worker (the same one used for Cloud OCR). The badge shows "University of Hyderabad Heritage segmenter ✓" when this succeeds.
- On-device fallback — if the service is unreachable, it falls back to a built-in rule-based assistant that handles the reliable cases (avagraha
ऽ, visarga sandhi). The badge then says "Guided assistant".
The overlay lets you edit the result directly:
- A live split-out line below the original verse shows the current split joined together, updating as you merge/split
- Each separated word is a chip showing its meaning translated in your chosen language beneath it
- A Meaning in: ગુ Gujarati / हि Hindi / EN English toggle switches all word meanings (defaults to Gujarati; remembers your choice)
- Tap a word to also see its full Monier-Williams dictionary entry, and 🔊 to hear it
- · merge · between any two words joins them back
- ✂ on a word opens an inline picker right in that row — tap a cut-point between letters to split there (no separate popup)
- 🔊 Chant all padas speaks the separated words in sequence
- 💾 Save remembers your split for that verse; 🔄 Re-split from API re-queries; ↺ Reset restores the original
Honest limitation: Sanskrit segmentation is inherently ambiguous — even the academic engine returns the ranked best analysis, not a guaranteed-correct one. And per-word translations come from Google Translate, which is weak on isolated/inflected Sanskrit words — treat the inline translation as a quick gloss and the tap-for-dictionary (Monier-Williams) as the more reliable source. The split/merge editing exists precisely so you have the final say. Treat all of it as a study aid.
Setup: Split Sandhi reuses your Cloud OCR Worker URL. If you haven't set that up (see Cloud OCR setup), it uses the on-device assistant only. Per-word meanings require online lookups to be enabled (same consent as Show Meanings).
Correcting language & text
Automatic script detection is good but not perfect, especially on OCR'd pages. Two manual overrides give you the final say, and both persist (saved in your browser):
Language override — long-press any word (or right-click on desktop) to open a picker:
- Set that word to Sanskrit / Hindi / Gujarati, or back to Auto-detect
- Or set the whole line's language — this is what controls the narration voice (voice is chosen per line)
Use this when a Sanskrit verse is being read in the Hindi voice, or a word shows the wrong dictionary.
Text correction — when OCR garbles a verse, use Re-OCR a region (in Document View) to fix the actual text. Corrections are keyed to the original text and survive re-processing.
Vedabase deep links
For recognized scriptures (Śrīmad-Bhāgavatam, Bhagavad-gītā), each verse gets a 📖 link to the official Vedabase entry:
- Filename hint helps: name files like
SB_3.25.pdforBG_2.pdffor auto-detection - Source chip shows "SB — set canto/ch" — tap to enter chapter/verse context
- Once context is set, every verse line has
📖 SB 3.25.6 on vedabase.io ↗pointing to the official commentary
Manual reference entry (when auto-detection fails): if a Sanskrit verse has no working link, it shows a 🔗 Set reference button. Tap it to enter the details by hand:
- Choose the scripture (Śrīmad-Bhāgavatam or Bhagavad-gītā)
- Enter Skand, Adhyay, and Shlok (for BG, just Adhyay and Shlok)
- A live preview shows the resulting vedabase.io link before you save
- Once saved, the verse shows its 📖 link, and a ✎ appears to edit it later
Your manual references are saved (keyed to the verse text) and persist across reloads and re-processing. Use ↺ Clear my reference in the dialog to remove one.
Vedabase content is BBT-copyrighted; we deep-link only, never copy text.
OCR for scanned documents
OCR (Optical Character Recognition) extracts text from images and scanned PDFs.
When to use OCR
| File type | OCR needed? |
|---|---|
| Digital PDF with embedded text | No — text extracted directly (perfect accuracy) |
| Scanned PDF (image-only) | Yes — turn on Force OCR |
| JPEG / PNG of a page | Yes — runs automatically |
| DOCX / RTF / TXT / HTML | No — text already structured |
Force OCR toggle
Located near "Clear All" on the main screen. Turn ON when:
- You uploaded a scanned PDF and see "No Indic text found"
- The PDF's embedded text is garbage (font mapping broken)
- You want to re-run OCR with a different engine
Local OCR (default)
Uses Tesseract.js running entirely in your browser. Free, private, works offline once language data is cached. Slower (~5–15 seconds per page) and less accurate than cloud OCR on poor scans.
Language data (~5MB for Gujarati + Devanagari) is downloaded from tessdata.projectnaptha.com on first OCR, then cached.
OCR quality indicator
After processing, a small badge near each source shows which engine processed it:
- ☁️ Google (green) — Google Cloud Vision (via your Cloudflare Worker)
- ☁️ OCR.space (green) — OCR.space free tier
- 📱 Local (grey) — Tesseract.js fallback
A confidence banner appears for low-quality results (< 60% confidence) — verify against the original page.
You can tap the engine badge any time to see a per-page diagnostic: which engine OCR'd each page, your current settings, and why cloud failed (if it did).
Re-OCR a region
When OCR mis-reads part of a page — most commonly an inline Sanskrit verse embedded in Gujarati/Hindi prose — you can re-OCR just that area instead of the whole page.
In Document View, each page has a 🔍 Re-OCR a region button. It is pinned to the top of the page (sticky), so it stays visible no matter how tall the page image is — you never have to scroll to the bottom to find it.
- Tap it, then drag a box over the text that was mis-read
- A dialog shows the cropped image and runs OCR automatically
- Choose the language for that region — Sanskrit only (default, best for verses), Gujarati only, Hindi only, or Auto
- Choose the engine — ☁️ Google Vision (if configured) or 📱 Local
- Edit the result by hand in the text box if needed
- Apply it in one of three ways (below)
Three ways to apply the result:
- Replace whole line(s) — tick one or more lines in the checklist and tap ✓ Replace selected line(s). For a multi-line region, tick all the lines it covers; the OCR result is split by line and matched to them top-to-bottom. (More result lines than ticked → the surplus folds into the last line; fewer → the extra ticked lines are removed.)
- Replace only part of a line — tap ✎ Replace only part of a line, choose the line (its full text loads into an editor), select the portion to fix (or place the cursor), tap ⤵ Put OCR result here, then ✓ Save line. Use this when the crop covers just a fragment, so the rest of the line is preserved.
- Single line — tick exactly one line and replace it, as before.
All edits and removals are keyed to the original text and persist across re-processing.
Why this helps: OCR engines assign one dominant script per visual line, so short inline Sanskrit gets misread as the surrounding script. Cropping just the verse and forcing Sanskrit-only removes that conflict and usually reads the verse correctly. If the scan is poor, you can also just type the correction directly — re-OCR is optional.
This works with local OCR too — no cloud account needed for the on-device option.
Fix a single line from Text View
You don't have to go to Document View to fix one line. In Text View each line has a small ✏️ button (next to the ☆); it glows amber when the line contains low-confidence words. Tapping it opens Fix line:
- Edit the text by hand in the box (correct script font), then ✓ Save correction — the fix is keyed to the line's original text and persists across re-processing and reopen, even if you edit the same line again later.
- 🔁 Re-OCR this line — pick an engine (☁️ Google Vision or 📱 Local) and a language (Sanskrit / Gujarati / Hindi / Auto); it crops just that line from the page image, runs OCR, and drops the result into the editable box for review before you save. Recovers a mangled verse without reprocessing the whole PDF.
(Re-OCR needs the line's position on the page image, so it's available for OCR'd PDF/image pages; for pasted or native-text PDFs the dialog offers manual edit only.)
Low-confidence flagging
With ⚠️ Check OCR on (Prefs strip), words the OCR engine reported low confidence for get a wavy amber underline (a tooltip shows the confidence). Both Google Vision and local Tesseract provide per-word confidence; it's stored in the OCR cache so flags persist on reopen.
Cloud OCR setup
Cloud OCR significantly improves accuracy on poor scans, photos, and books with marginalia. Optional and opt-in — disabled by default.
Why a proxy?
Google Cloud Vision blocks direct browser calls (CORS). The app uses a free Cloudflare Worker as a proxy between your browser and Google. Your API key lives on Cloudflare as an encrypted secret — never exposed in browser source code.
One-time setup (≈ 10 minutes)
Two parts:
Part 1: Get a Google Cloud Vision API key
- Go to console.cloud.google.com
- Create a new project (e.g.
indic-reader) - Billing → Link a billing account (free tier covers 1,000 calls/month at $0, but a card is required)
- Billing → Budgets & alerts → Create budget: set amount to with default thresholds — this emails you BEFORE any charge
- APIs & Services → Library: search "Cloud Vision API", click ENABLE
- APIs & Services → Credentials → + Create credentials → API key
- Copy the key. Click "Edit API key":
- API restrictions: Restrict key → check ONLY "Cloud Vision API"
- Application restrictions: None (Worker calls don't preserve referrer)
- SAVE
Part 2: Deploy a Cloudflare Worker
- Go to dash.cloudflare.com → Workers & Pages
- Create → Hello World → name it (e.g.
indic-ocr-proxy) → Deploy - Open Worker → Edit code → delete the template → paste the entire
cloudflare-worker-google-ocr.jsfile - Save and Deploy
- Worker → Settings → Variables and Secrets, add two:
GOOGLE_API_KEY= (your key from Part 1) — mark as Encrypt (Secret)ALLOWED_ORIGIN= your app's URL (e.g.https://indic-reader.pages.dev) — use*only for testing
- Save
Part 3: Configure the app
- In the app, tap ⚙ Cloud OCR (gear icon near Cloud OCR toggle)
- Provider: ⭐ Cloudflare Worker → Google Vision
- Paste your Worker URL (e.g.
https://indic-ocr-proxy.YOUR.workers.dev) - Tap 🔍 Test connection — should show
✓ Connection works. Detected: "TEST" - Save credentials
- Flip ☁️ Cloud OCR toggle ON
OCR.space alternative (Gujarati only, no setup)
For Gujarati-only documents and quick testing:
- Get a free key at ocr.space/ocrapi/freekey
- ⚙ Cloud OCR → choose OCR.space → paste key → save
Free tier: 25,000 pages/month. No Sanskrit support in free tier.
Cost reality
| Provider | Free tier | After free tier |
|---|---|---|
| Google Cloud Vision | 1,000 calls/mo | |
| OCR.space | 25,000 calls/mo | Need paid plan |
| Cloudflare Workers | 100,000 calls/day | $0.50 per million |
For personal reading (a few dozen pages a day), you'll never exceed any free tier. Set the
Saving credentials to your device's password manager
In the Cloud OCR settings dialog, 🔒 Save to device stores your API key (OCR.space) or Worker URL (Google proxy) in your operating system's secure password store via the browser's Credential Management API — Google Password Manager on Android, iCloud Keychain on iOS where supported, or the Windows credential store. It's protected by your device lock, and the app never sees it again except when you tap ↺ Restore from device. Availability depends on your browser/OS; where programmatic storage isn't supported the key simply stays in on-device storage as before.
Google Drive integration
Optional and OAuth-gated. Lets you pick files directly from your Drive instead of downloading then uploading.
Setup
- In Google Cloud Console (same project as Vision), enable:
- Google Drive API
- Google Picker API
- Create credentials:
- OAuth 2.0 Client ID (Web application)
- Authorized JavaScript origins: your app URL (e.g.
https://indic-reader.pages.dev)
- Authorized JavaScript origins: your app URL (e.g.
- API key (separate from the Vision key)
- API restrictions: Drive API + Picker API
- Application restrictions: HTTP referrers →
https://YOUR-APP-URL/*
- OAuth 2.0 Client ID (Web application)
- OAuth consent screen → set up as External + Testing → add your own email as Test User
- In the app: tap 🟢 Drive → enter OAuth Client ID and API key → save
Usage
- Tap 🟢 Drive → 🔐 Sign in & pick from Drive
- Google's Picker UI opens; select files
- Files are downloaded into the app and processed
Drive integration is fiddly to set up. If you have trouble, the local file picker works perfectly without any setup.
Settings & preferences
Saved automatically in browser localStorage:
| Setting | Default | Description |
|---|---|---|
| Theme | Auto | Light / dark / follow system |
| Layout | A | Single column |
| Voice per language | First available | Use Voice Settings to choose |
| Speed per language | 1.0× | Three sliders |
| Bookmarks (★ stars) | None | Per-line favorites |
| Show Meanings consent | Off | Required for Sanskrit dictionary |
| Translate consent | Off | Required for verse translation |
| Cloud OCR consent | Off | Required for cloud OCR |
| Drive credentials | None | OAuth Client ID + API key |
| Cloud OCR credentials | None | Worker URL or OCR.space key |
Reset privacy choices
Bottom of the page → "Reset privacy choices" — revokes all consents and re-shows the privacy banner. Doesn't delete credentials.
Clear All
Top of the file-input section → wipes loaded documents and in-memory state. Doesn't touch settings or credentials.
Keyboard shortcuts
Desktop only:
| Key | Action |
|---|---|
| Space | Play / pause narration |
| ← | Previous line |
| → | Next line |
| ↑ | Decrease speed |
| ↓ | Increase speed |
| T | Toggle Text / Document view |
| F | Cycle language filter (All → Guj → Sans → Hin → All) |
| S | Toggle Auto-scroll |
| Esc | Stop narration / close dialogs |
Privacy & data handling
Files stay on your device by default. The app runs entirely in your browser — there's no Indic Reader server.
What's sent externally and when
| Feature | Sends data to | When |
|---|---|---|
| Cloud OCR | Cloudflare Worker → Google Vision (or OCR.space) | Each page processed (only when toggle is ON) |
| Re-OCR a region | Cloudflare Worker → Google Vision (or OCR.space) | When you re-OCR a selected region with a cloud engine (local option sends nothing) |
| Fix a single line / Continuous next-page OCR | Cloudflare Worker → Google Vision (or OCR.space) | When you re-OCR one line, or when Continuous mode prepares the next page, with a cloud engine (local option sends nothing) |
| Split Sandhi | Cloudflare Worker → UoH Heritage segmenter (verse text only, no files); Google Translate for per-word meanings | When you tap "Split Sandhi" (segmenter falls back to on-device if unreachable; meanings need online lookups enabled) |
| Show Meanings | Cologne University MW API + Google Translate | When you tap "Show Meanings" |
| Translate verse | Google Translate (public endpoint) | When you tap "Translate verse" or use layout B/C |
| Translate page (Gu↔Hi, →EN) | Google Translate (public endpoint) | When you tap 🌐 ગુ→हि / हि→ગુ / →EN (Gujarati/Hindi text only; Sanskrit verses not sent) |
| Vedabase link | vedabase.io (link only, no content) | When you tap the 📖 link (opens in new tab) |
| Drive Picker | Google (drive.google.com) | When you tap Drive sign-in |
| First-launch CDN | cdn.jsdelivr.net | Loading PDF/OCR/DOCX libraries (cached afterward) |
| Tesseract language data | tessdata.projectnaptha.com | First local OCR run (cached afterward) |
What's NOT sent
- No analytics or telemetry
- No tracking pixels
- No cookies
- No data sent to Anthropic or any other party
- Your text is never sent anywhere unless you explicitly trigger an external feature
Stored only on your device (never uploaded)
Your preferences and manual corrections live in this browser's localStorage and are never sent anywhere: language/text overrides, line text corrections (inline edit / re-OCR), manual verse references (the canto/chapter/verse you enter for vedabase.io links), saved sandhi splits, translation cache, bookmark notes, voice/theme preferences, the Prefs toggles, and Drive credentials. The OCR cache, the last-opened document (for the Continue button), and your reading position are stored in this browser's IndexedDB, on-device only. Optionally, the 🔒 Save to device button places your OCR credential in your OS password manager (Google Password Manager / iCloud Keychain / Windows), not in the app. A manual verse reference only ever produces a vedabase.io link, sent solely when you tap that link.
Your data rights (GDPR/DPDP)
- Access/Portability: use "Copy Text" to export all extracted content
- Erasure: "Clear All" wipes in-memory state; browser "Clear site data" wipes preferences
- Withdraw consent: "Reset privacy choices" link at the bottom
Troubleshooting
"No Indic text found"
Usually means the document has no embedded text. Solutions:
- Turn ON Force OCR, re-process
- If the file is a Google Docs PDF, font mapping may be broken — Force OCR fixes it
- For images: make sure the script is clearly readable (clean scan, no glare)
Cloud OCR shows "Local (cloud failed)"
First, tap the engine badge — it now shows a per-page diagnostic: your current settings (Cloud ON/OFF, fallback ON/OFF), which engine OCR'd each page, and the run log with the specific failure reason. This tells you exactly what happened.
If a page genuinely fell back to local, common causes:
- Worker URL wrong or Worker not deployed
- Google API key invalid or restrictions blocking it
- ALLOWED_ORIGIN on Worker doesn't match your app URL
- Free quota exceeded
- Network down
Note: the badge reflects the last processing run. Turning Cloud OCR on does not re-OCR already-loaded pages — you must re-process the page (or use Re-OCR a region for a targeted fix).
Sanskrit being read as Gujarati (or vice versa)
Two fixes:
- Quick fix (classification): long-press the word → set its language, or "Set line → Sanskrit" to fix the narration voice. See Correcting language & text.
- Root fix (garbled text): if the text itself is wrong from OCR, use Re-OCR a region in Document View to re-read just that verse with Sanskrit-only hints.
The underlying cause is a known OCR limitation: inline script-switching within a single line confuses every OCR engine. Standalone verses work; short embedded quotes may misread.
"Drive Picker error"
Usually means API key restrictions are too strict. Quick fixes:
- Edit the Drive Picker API key in Google Cloud Console
- Application restrictions → temporarily set to "None"
- Wait 5 minutes for propagation
- Reload the app
If still broken, just skip Drive — use the regular file picker.
Narration sounds wrong / weird voice
System TTS voices vary by device. Tap Voice Settings in the reader and pick a better voice:
- Android: install "Google Text-to-Speech" + the language packs
- Windows: Settings → Time & Language → Speech → Add voices
- macOS/iOS: System Settings → Accessibility → Spoken Content → System Voice
For Sanskrit specifically, Hindi voices are usually the best fallback since true Sanskrit TTS is rare.
Page won't scroll on mobile
Pull down firmly to refresh. If the player bar is hiding the bottom of content, try landscape orientation. The bottom bar's height adapts but can occasionally need a refresh.
App still shows the old version number after an update
The footer shows the running APP_VERSION (e.g. Indic Reader v66). If you deployed a new build but still see the old number — even in incognito or another browser — the problem is almost always that the new files are not actually live yet, not a browser cache:
- Confirm the deploy happened. Open the raw page in a fresh incognito window (no service worker there) and check the footer version, or
View sourceand search forAPP_VERSION. A fresh browser fetches straight from the server, so whatever it shows is what the server is serving. If that's still old, the deploy didn't go live. - Check the right branch / build. On Cloudflare Pages or GitHub Pages, make sure the commit landed on the production branch and the build/deploy actually ran and succeeded (not a preview deployment URL).
- Bump both version strings.
APP_VERSIONinindex.htmlandCACHE_NAMEinsw.jsshould be bumped together so the service-worker cache is invalidated. - Edge cache. The included
_headerssetsCache-Control: no-cacheon/,index.html,sw.js,manifest.json,robots.txtandsitemap.xml, so the HTML and worker always revalidate. If you host elsewhere, ensure your host isn't serving HTML with a long cache TTL; purge the CDN cache after deploying. - Stuck service worker (already-installed users only): the worker is network-first for the page and calls
skipWaiting(), so a normal reload while online should update it. If a device is truly stuck, browser Settings → Clear site data for the domain forces a clean fetch.
Known limitations
Things this app does not and cannot do well:
- Vedic pitch accents (svara marks) — Web Speech API can't render them. Pre-recorded chanting audio would be needed.
- Real-time collaboration — no shared state across devices/users.
- Note-taking — bookmarks (★) now support a text note and store the full line, but there are no inline highlights or freehand annotations.
- Inline script-switching OCR — short embedded Sanskrit quotes in Gujarati prose often misread (limitation of all OCR engines, not specific to this app). Mitigated by Re-OCR a region or Fix a single line with Sanskrit-only hints, and manual text/language correction.
- Continuous next-page OCR gap — for an uncached page, narration can outrun the background OCR on large scans/slow connections, causing a brief pause at the page boundary (the few-line lead usually hides it; cached pages are instant).
- Device password manager support — 🔒 Save to device relies on the browser's Credential Management API; it works on Chromium (Android/Windows/desktop) and where iOS supports it, but isn't available in every browser.
- Vedabase content scraping — BBT-copyrighted; we deep-link to the official site only, never copy text.
- Translation quality for technical Sanskrit — Google Translate handles colloquial Sanskrit poorly. Use the MW dictionary for word meanings, treat full-verse auto-translation as approximate.
- Sandhi splitting is not guaranteed correct — the Heritage segmenter gives the ranked best analysis, but Sanskrit segmentation is inherently ambiguous. The on-device fallback is heuristic only. Always verify with the built-in split/merge editing. See Split Sandhi.
- Offline-first PWA install — the file works offline once loaded, but proper PWA install with home-screen icon requires the GitHub Pages bundle.
- Persistent translation cache — currently per-session. Reloading the page loses cached translations. (Language/text corrections, bookmark notes, the OCR cache, and saved sandhi splits do persist.)
- Screen-off narration on Android — Web Speech narration is suspended by the OS when the screen is manually locked; no web app can override this. The screen is held awake during narration as the practical workaround. True screen-off background audio needs a native app.
- Android Auto / CarPlay projected screen — a web app cannot register as a native in-car app, so Indic Reader can't appear as a tile on the car's Auto/CarPlay screen. Use Bluetooth audio mode in the car (see Background & car playback).
- iOS storage — Safari clears site data in Private Browsing, with "Block All Cookies" on, or after 7 days of non-use. Add to Home Screen for durable storage; a startup banner warns if storage is blocked.
- iOS Gujarati voice — iOS has no native Gujarati voice; the app transliterates to Devanagari and uses the Hindi voice so Gujarati is still spoken (Hindi-accented). Android with the Google Gujarati voice is recommended for proper Gujarati narration.
OCR reading order
For cloud-OCR'd pages, the app rebuilds the visual reading order at the line level — keeping each recognized line's words and punctuation intact and reordering only whole lines top-to-bottom — so multi-column headers and skewed scans don't scramble words. The OCR pipeline is versioned so improvements automatically invalidate stale cached results without any manual cache clearing.
File structure — what each file is for
The project ships in a few bundles. Here's what every file does and whether you need it.
Core app (the only truly required file)
| File | Purpose | Required? |
|---|---|---|
index.html |
The entire application — all HTML, CSS, and JavaScript in one self-contained file (~350 KB). Open it directly in a browser and everything works: reading, narration, OCR, meanings, sandhi splitting. Everything else is optional enhancement. | ✅ Yes |
Offline & install support (PWA)
| File | Purpose | Required? |
|---|---|---|
sw.js |
Service worker. Caches the app so it loads offline after the first visit, and powers "install to home screen". It is network-first for the page (so a new deploy is fetched immediately when online) and cache-first for static assets. The version string inside (e.g. indic-reader-v66) is bumped on every update to invalidate the old cache. |
Optional (enables offline) |
manifest.json |
PWA manifest — app name, description, icons, theme color, display mode, categories. Lets phones offer "Add to Home Screen" with a proper icon, and feeds app/store listings. | Optional |
icon-192.png |
App icon (192×192) for home screen / launcher. | Optional |
icon-512.png |
App icon (512×512) for splash screen / high-DPI, and the link-preview image. | Optional |
Discoverability (SEO)
| File | Purpose | Required? |
|---|---|---|
robots.txt |
Allows all search-engine crawlers and points them to the sitemap. | Recommended when hosting |
sitemap.xml |
Lists the site's URL(s) for search engines. Submit it in Google Search Console / Bing Webmaster Tools to speed up indexing. | Recommended when hosting |
The <head> of index.html also carries SEO metadata: a descriptive <title> and <meta name="description">/keywords, a <link rel="canonical">, Open Graph and Twitter card tags (for link previews), and JSON-LD structured data (WebApplication) describing the app, its features and languages. If you deploy on a domain other than indic-reader.pages.dev, update the canonical / og:url / twitter image URLs, the robots.txt sitemap line, and the <loc> in sitemap.xml to match.
Hosting & security (Cloudflare Pages / GitHub Pages)
| File | Purpose | Required? |
|---|---|---|
_headers |
Security headers for Cloudflare Pages — Content-Security-Policy (restricts which domains the app may contact), plus standard hardening headers. This is what allows the Worker, dictionary, and translate endpoints while blocking everything else. | Recommended when hosting |
_headers-no-csp |
A fallback version with the CSP relaxed, in case the strict policy ever blocks something on your setup. Rename to _headers only if you hit a CSP problem. |
Optional |
.github/workflows/deploy.yml |
GitHub Actions workflow that auto-deploys the repo to GitHub Pages on every push. Only relevant if you host on GitHub. | Optional (GitHub only) |
README.md |
This documentation file. | Reference |
Cloud OCR & Sandhi (the Cloudflare Worker)
| File | Purpose | Required? |
|---|---|---|
cloudflare-worker-google-ocr.js |
The Worker proxy — paste this into a Cloudflare Worker (it is not part of the website; it runs separately on Cloudflare's edge). It does two jobs: (1) proxies page/region images to Google Cloud Vision for OCR, and (2) proxies verse text to the UoH Heritage segmenter for Split Sandhi. Both are needed only because browsers can't call those services directly (CORS). Needs your Google API key set as a Worker secret. | Only for Cloud OCR & API sandhi |
cloudflare-worker-azure-ocr.js |
An alternative Worker using Azure Computer Vision instead of Google, if you prefer Azure. Use one or the other, not both. | Optional alternative |
Convenience bundles & local testing
| File | Purpose | Required? |
|---|---|---|
indic-reader-github.zip |
Zipped copy of the GitHub Pages bundle (app + PWA + headers + workflow), ready to upload to a repo. | Convenience |
indic-reader-standalone.zip |
Zipped copy of the standalone/PWA bundle for offline or self-hosting. | Convenience |
serve.py |
A tiny local web server (Python, in the standalone/PWA bundle) — run python serve.py to test the app at localhost with correct headers, since some features need http:// rather than file://. |
Local testing only |
android-wrapper/, ios-wrapper/, windows-wrapper/ |
Thin native wrappers (in the standalone/PWA bundle) that load the app in a WebView, if you want to package it as an installable app for each platform. | Optional |
Minimal setups
- Just try it:
index.htmlalone. - Offline-capable website:
index.html+sw.js+manifest.json+ both icons +_headers(androbots.txt+sitemap.xmlfor search engines). - Full features (Cloud OCR + API sandhi): the above, plus deploy
cloudflare-worker-google-ocr.jsto a Cloudflare Worker and paste its URL into the app's Cloud OCR settings.
Credits & sources
- Monier-Williams dictionary: Cologne South Asia Studies / cologne-digital-sanskrit-lexicon
- Sandhi segmentation: University of Hyderabad Sanskrit Heritage / Saṃsādhanī tools (sanskrit.uohyd.ac.in)
- OCR: Tesseract.js (local), Google Cloud Vision (cloud)
- DOCX parsing: mammoth.js
- PDF rendering: pdf.js (Mozilla)
- Sanskrit transliteration: IAST scheme
- Vedabase: deep links to vedabase.io (BBT Inc.)
License & copyright
App code is provided for educational and personal study use. Scripture texts loaded into the app retain their original copyrights — verify your right to OCR or process any document before doing so. Vedabase content is BBT-copyrighted; this app links to but does not redistribute their text.
No commercial redistribution of this app or its output.