/building · 2024

Translation of the Bhagavad Gita

sanskrit · word-by-word · audio

A translation of the Bhagavad Gita on gita.pub with every Sanskrit word individually glossed, recitations from five narrators, and the whole dataset in a public git repository.

A verse page on gita.pub, with an illustration above the translation and footnote links on each phrase

I wanted to create a translation that inspires people to form their own understanding of the text. On gita.pub, the original Sanskrit words are individually translated, and the full translation is written as a cohesive narrative.

A verse page on gita.pub, with an illustration above the translation and a footnote marker on each phrase
Every superscript on the translation opens the Sanskrit behind that phrase, word by word.

A directory per verse#

The dataset is a tree of flat files, one directory per verse: the Devanagari text, translations, an optional prose summary in markdown, and a word.json holding the part I care most about. Every verse is split into words, and every word carries its Sanskrit, a transliteration, a gloss, and often a short note. Sanskrit glues words together enthusiastically, so compounds get a part array that splits them again:

{
    "en": "dharma-kṣhetre",
    "means": "the land of Dharma",
    "sa": "धर्मक्षेत्रे",
    "part": [
        { "en": "dharma", "means": "Dharma", "sa": "धर्म" },
        { "en": "kṣhetre", "means": "land area", "sa": "क्षेत्रे" }
    ]
}

Across the whole text that comes to about 11,700 word entries, 1,500 of which are compounds that break down further, and 600 so far with longer etymological notes. Because the glossary is data, the website, the PDF edition (typeset with pdfkit from the same files), and the search index all fall out of a single source.

A chapter page on gita.pub, with a summary in the sidebar beside the chapter title
A chapter opening. The summary in the sidebar is one of the optional markdown files in the verse tree.

Transliterating Devanagari#

Devanagari is an abugida, where every consonant carries an implicit short a unless a vowel sign replaces it or a virama mark cancels it.

क is ka, कि is ki, क् is bare k.

This makes character-for-character transliteration a bit more complicated. To transliterate Devanagari to its ISO 15919 romanization, the transliterator on the site handles this with a placeholder pass. Every consonant is first emitted carrying a capital A (क becomes kA), vowel signs become their roman vowels, and the virama becomes a zero-width space. A second pass decides what happens to each placeholder: deleted when the zero-width space follows, absorbed into a full vowel that follows, and lowercased into the inherent a otherwise. What this actually looks like is a massive chained regex replacement, and the same trick emits both IAST and ISO 15919 romanization.

Verse audio without verse files#

Five narrators recite the Gita on the site, each recording running a full chapter, about a gigabyte of mp3s in all. The player, though, works at the verse level: you are reading verse 2.47 and you want to hear verse 2.47.

How do you play one verse out of a twenty-minute chapter recording?show me

You never cut the file. Each verse's JSON stores a start and end timestamp per narrator, and the player seeks within the chapter recording it already has. I wrote a small silence detector around ffmpeg's silencedetect filter to propose the boundaries, since chanted recitation leaves honest pauses between verses, but most timestamps were tagged the dependable way, by listening with a finger on the pause button.

Five narrators across seven hundred verses would have meant thirty-five hundred audio files to cut, name, and upload. Instead there are ninety, and stepping between adjacent verses costs nothing because the browser already has the chapter.

Git is the database#

There is no backend. npm run dev starts a local server that renders the same templates as the production site with editing controls added, and every edit writes straight back to the JSON on disk. Contributing a correction means opening a pull request, and reviewing one means reading the diff of a single verse.

The build renders every page ahead of time through nunjucks templates and syncs the output to S3, comparing checksums so only changed files upload, then finishes with a CloudFront invalidation.

Cross-references are the one place a language model gets involved. Each verse's English translation is embedded with text-embedding-3-small and stored in one unglamorous JSON file. At build time every verse page links to its nearest neighbors, dropping anything that scores below 80 percent of the best match, so a verse with no real siblings shows fewer links rather than worse ones. The same machinery matches Gita verses against ten Upanishads in the dataset, which also holds the Ashtavakra Gita, the Yoga Sutras, and the Vishnu Sahasranama.

The goal is to keep the original where a curious reader can reach it, and to make every claim about every word something you can check. Summaries exist for a fraction of the verses, the roadmap still lists more Upanishads and a video series, and I expect to be working on it for a long time, happily.

cd /building