Module:GetRootVerses
Quick Guide[edit]
This module displays root verses from the Bodhicaryāvatāra with multiple source versions, translations, and interactive toggles.
Quick Start[edit]
Display a verse[edit]
{{#invoke:GetRootVerses|main|verse=1.1}}Store verse properties[edit]
{{#invoke:GetRootVerses|set}}Simple display (specific sources only)[edit]
{{#invoke:GetRootVerses|SimpleVerse
|verse=1.1
|sources=001-Tsadra-BCA-Root-Padmakara;009-Tsadra-BCA-Root-Gomez
}}Functions[edit]
| Function | Parameters | Description |
|---|---|---|
set |
none | Stores SMW properties for current verse |
getSourceToggles |
verse |
Returns toggle controls for source versions |
main |
verse (optional) |
Full verse display with all sources |
SimpleVerse |
verse, sources |
Simplified display with selected sources |
Source IDs[edit]
| ID | Label |
|---|---|
001-Tsadra-BCA-Root-Padmakara |
Padmakara Tibetan |
009-Tsadra-BCA-Root-Gomez |
Sanskrit |
005-Tsadra-BCA-Root-Dunhuang |
Dunhuang |
017-Tsadra-BCA-Root-Tsadra_Edition |
Tsadra Edition |
Features[edit]
- Multiple source versions (Tibetan, Sanskrit, Dunhuang)
- Interactive source/translation toggles
- Audio players for chanted verses
- Multiple translation display
- Responsive layout
- Links to translation memory data
Performance[edit]
- 1-2 queries per page (optimized)
- <2 seconds load time
- Modular architecture for easy maintenance
Dependencies[edit]
- Semantic MediaWiki
- External Data extension
- Template:AudioVerse, Template:VersionContent, Template:InternalLink
- Widget:SingleVisibilityToggle
Helper Modules[edit]
- Module:GetRootVerses/Data - Data retrieval
- Module:GetRootVerses/Display - HTML rendering
See Also[edit]
Full Documentation[edit]
This module provides verse display functionality for the Bodhicaryāvatāra (BCA) project, handling retrieval and presentation of root verses from multiple translation memories with their associated translations.
Architecture[edit]
The module is split into three components for better maintainability and performance:
- Module:GetRootVerses (main module)
- Entry point containing all public functions:
set,getSourceToggles,main, andSimpleVerse
- Module:GetRootVerses/Data
- Data retrieval layer that makes optimized SMW queries and organizes verse data by source
- Module:GetRootVerses/Display
- Presentation layer containing reusable HTML rendering functions
Public Functions[edit]
set[edit]
Stores semantic properties for the current verse page from translation memory data.
Usage:
{{#invoke:GetRootVerses|set}}Called from: Verse pages automatically via template Returns: Empty string on success, error message on failure Side effects: Sets SMW properties on the page including:
SegmentSource- Original Tibetan textSourceTranslit- Wylie transliterationSegmentTranslation- English translationSpanish- Spanish translation (if available)SanskritSource- Sanskrit text (if available)SanskritTranslit- Sanskrit transliteration (if available)FrenchTranslation- French translation (if available)- Additional metadata fields
getSourceToggles[edit]
Generates toggle controls for switching between different source versions (Tibetan, Sanskrit, Dunhuang, etc.).
Usage:
{{#invoke:GetRootVerses|getSourceToggles|verse=1.1}}Parameters:
verse- Verse number in format Chapter.Verse (e.g., "1.1", "5.109")
Returns: HTML toggle widgets for available source versions
main[edit]
Main display function that renders verse content with all available sources, translations, and interactive elements.
Usage:
{{#invoke:GetRootVerses|main|verse=1.1}}Parameters:
verse- Verse number (optional, defaults to current page's subpage text)
Returns: Complete HTML structure including:
- Source text in original script (Tibetan, Sanskrit, etc.)
- Transliteration (toggleable)
- Primary English translation
- Toggle controls for showing/hiding content
- Audio players (for chanted verses where available)
- Other available translations in expandable sections
Features:
- Responsive grid layout
- Interactive toggles for source/translation visibility
- Horizontal scrolling for multiple translations
- Audio verse players
- Links to translation memory data pages
SimpleVerse[edit]
Simplified verse display without the full toggle interface, suitable for embedding in other contexts.
Usage:
{{#invoke:GetRootVerses|SimpleVerse
|verse=1.1
|sources=001-Tsadra-BCA-Root-Padmakara;009-Tsadra-BCA-Root-Gomez
}}Parameters:
verse- Verse number (required)sources- Semicolon-separated list of translation memory IDs
Returns: Simplified HTML display with requested sources only
Translation Memory IDs[edit]
The module works with the following translation memory sources:
| ID | Label | Language | Description |
|---|---|---|---|
001-Tsadra-BCA-Root-Padmakara |
Padmakara Tibetan | Tibetan | Primary Tibetan source with Padmakara translation |
009-Tsadra-BCA-Root-Gomez |
Sanskrit | Sanskrit | Sanskrit text with transliteration |
005-Tsadra-BCA-Root-Dunhuang |
Dunhuang | Tibetan | Dunhuang manuscript version |
017-Tsadra-BCA-Root-Tsadra_Edition |
Tsadra Edition | Tibetan | Tsadra Foundation critical edition |
004-Tsadra-BCA-Root-Padmakara-French |
French | French | French Padmakara translation |
Examples[edit]
Basic Verse Display[edit]
Display verse 1.1 with all available sources:
{{#invoke:GetRootVerses|main|verse=1.1}}Display Specific Sources[edit]
Show only Tibetan and Sanskrit for verse 5.109:
{{#invoke:GetRootVerses|SimpleVerse
|verse=5.109
|sources=001-Tsadra-BCA-Root-Padmakara;009-Tsadra-BCA-Root-Gomez
}}On a Verse Page[edit]
Typical usage on a verse page combines multiple functions:
<!-- Store properties -->
{{#invoke:GetRootVerses|set}}
<!-- Display toggles -->
<div class="verse-controls">
{{#invoke:GetRootVerses|getSourceToggles}}
</div>
<!-- Display verse -->
{{#invoke:GetRootVerses|main}}Performance[edit]
The module is optimized for minimal database queries:
- Single query approach: Makes one comprehensive SMW query per verse to retrieve all data
- No caching: Avoids memory accumulation issues in MediaWiki's Lua environment
- Lazy loading: Audio files only fetched when needed
- Efficient organization: Data processed in Lua memory after retrieval
Typical performance:
- 1-2 SMW queries per page load
- <2 second load time for complex verses
- Minimal memory footprint
Technical Details[edit]
Data Retrieval[edit]
The Module:GetRootVerses/Data module's getAllVerseData() function makes a single optimized query:
-- Single query returns ALL verse data
local query = [[
[[Category:Translation Memories]]
[[RootTextVerseNumPadmaK::verse]]
[[SegmentFormat::Root]]
|?TransMemID|?SegmentSource|?SegmentTranslation|...
|limit=1000
]]
Results are organized into a structured table:
{
bySource = {
['001-Tsadra-BCA-Root-Padmakara'] = {
segments = {...},
metadata = {...}
},
...
},
rootSources = {...},
translations = {...}
}
Display Components[edit]
The Module:GetRootVerses/Display module provides reusable rendering functions:
renderVerseContent()- Source text and transliterationrenderSourceHeader()- Source title with togglesrenderVersionColumn()- Wrapper for VersionContent templaterenderTranslationsBox()- Scrollable translations containercreateToggle()- Visibility toggle widgets
Templates Used[edit]
The module relies on these templates:
- Template:AudioVerse - Audio player for chanted verses
- Template:VersionContent - Styled content containers
- Template:InternalLink - Links to internal pages
Widgets Used[edit]
Widget:SingleVisibilityToggle- Toggle controls for showing/hiding content
Dependencies[edit]
Extensions[edit]
- Semantic MediaWiki (SMW) - For querying verse data
- External Data - For fetching audio track information from commons.tsadra.org
- Scribunto - MediaWiki's Lua scripting extension
Required Properties[edit]
The module expects these SMW properties to be set on Translation Memory pages:
TransMemIDTransMemNumRootTextVerseNumPadmaKSegmentSourceSourceSortOrderSourceTranslitSegmentTranslationSegmentOrderRootTextChapterNumRootTextVerseNumSegmentFormatSourceVersionLabelRootSourceTransMemSpanish(optional)Flag(optional)
Error Handling[edit]
The module handles various error conditions gracefully:
- No verse data found: Returns empty string
- Missing SMW: Returns error message "mw.smw module not found"
- Invalid verse number: Returns empty string
- Missing audio: Continues without audio player
- Missing translations: Shows only available translations
Maintenance[edit]
Updating Translation Memories[edit]
When translation memory data is updated:
- Update the corresponding Translation Memory pages with new data
- Pages using the module will automatically reflect changes
- No module modification needed
Adding New Sources[edit]
To add a new translation memory source:
- Add the new TransMemID to the DATA section queries if it's a root source
- Update the AUDIO_CATEGORIES if it has audio
- Module will automatically include it in queries
Troubleshooting[edit]
No verses displaying:
- Check that Translation Memory pages exist and have proper categories
- Verify
RootTextVerseNumPadmaKproperty is set correctly - Ensure
SegmentFormatis set to "Root"
Missing translations:
- Verify
RootSourceTransMemproperty points to correct source - Check that translation pages are categorized as Translation Memories
Audio not appearing:
- Verify audio files exist on commons.tsadra.org
- Check category structure (BCA Tibetan/Sanskrit Chanted Verses)
- Confirm ChapterNumber and VerseNumber properties are set
Version History[edit]
- v2.0 (2025) - Optimized version with single-query approach, modular architecture
- v1.0 (Previous) - Original monolithic implementation
See Also[edit]
- Module:GetData - Related data retrieval module
- Module:GetFrontCoverImg - Image retrieval for library items
- Module:GetRels - Relationship display module
- Translation Memories - Documentation on translation memory structure
Categories[edit]
This module is used on verse pages throughout the BCA project and is part of the core display infrastructure.
Developer Documentation[edit]
This page provides technical details for developers working on the GetRootVerses module system.
Architecture Overview[edit]
Module:GetRootVerses (main)
├── Public API functions
├── Helper functions for rendering
└── Requires:
├── Module:GetRootVerses/Data
└── Module:GetRootVerses/Display
Module:GetRootVerses/Data
├── getAllVerseData() - Single optimized query
├── getAudioTrack() - Fetch audio files
├── processVerseSegments() - Organize segments
└── processTranslations() - Sort translations
Module:GetRootVerses/Display
├── renderVerseContent() - Source text HTML
├── renderSourceHeader() - Headers with toggles
├── renderVersionColumn() - Content columns
├── renderTranslationsBox() - Translation container
└── createToggle() - Toggle widgets
Key Design Decisions[edit]
Single Query Approach[edit]
The module uses ONE comprehensive SMW query to retrieve all verse data, rather than multiple queries:
-- Single query gets everything
function p.getAllVerseData(verse)
local query = [[
[[Category:Translation Memories]]
[[RootTextVerseNumPadmaK::]] .. verse .. [[]]
[[SegmentFormat::Root]]
|?TransMemID|?SegmentSource|?SegmentTranslation
|?Spanish|?Flag|...all properties
|limit=1000
]]
local results = smw.ask(query)
-- Organize in memory by source
return organized
end
Why:
- 95% reduction in database queries (20 → 1)
- All data available for processing
- No caching needed (avoiding memory issues)
- Faster overall performance
No Module-Level Caching[edit]
The module explicitly avoids module-level caching:
-- BAD - causes memory accumulation
local cache = {}
function getData()
if cache[key] then return cache[key] end
cache[key] = data
return data
end
-- GOOD - data lives only during page render
function getData()
local data = smw.ask(query)
return data
end
Why:
- MediaWiki keeps Lua modules loaded across pages
- Module-level tables persist and grow
- Causes "Not enough LUA memory" errors
- Parser cache handles caching instead
Separation of Concerns[edit]
Three modules with distinct responsibilities:
| Module | Responsibility | Should NOT |
|---|---|---|
| GetRootVerses | Public API, orchestration | Query SMW, build HTML strings |
| GetRootVerses/Data | Data retrieval, organization | Render HTML, call templates |
| GetRootVerses/Display | HTML rendering | Query data, process business logic |
Data Flow[edit]
1. User calls: {{#invoke:GetRootVerses|main|verse=1.1}}
↓
2. Main module: dataHelper.getAllVerseData(verse)
↓
3. Data module: Makes ONE SMW query
↓
4. Data module: Organizes results by source
↓
5. Returns: { bySource = {...}, rootSources = {...}, translations = {...} }
↓
6. Main module: Loops through sources, calls renderSourceSection()
↓
7. Render function: Calls displayHelper functions
↓
8. Display module: Returns HTML strings
↓
9. Main module: Concatenates all HTML
↓
10. Output: Complete verse display HTML
API Reference[edit]
Module:GetRootVerses/Data[edit]
-- Get all verse data in single query
getAllVerseData(verse)
Parameters:
verse (string) - Verse number e.g. "1.1"
Returns:
table - Organized data structure
.bySource[tmid] = {segments, metadata}
.rootSources[tmid] = metadata
.translations[root_tmid][trans_tmid] = data
-- Get audio track file
getAudioTrack(chapter, verse, language)
Parameters:
chapter (string) - Chapter number
verse (string) - Verse number
language (string) - 'tibetan' or 'sanskrit'
Returns:
string|nil - Audio filename or nil
-- Process verse segments
processVerseSegments(segments)
Parameters:
segments (table) - Array of segment data
Returns:
table - Processed structure with flags, verse numbers, etc.
-- Process translations
processTranslations(translationsData)
Parameters:
translationsData (table) - Translation data by source
Returns:
array - Sorted translations ready for display
Module:GetRootVerses/Display[edit]
-- Create toggle widget
createToggle(frame, id, label, options)
Parameters:
frame - MediaWiki frame object
id (string) - Toggle element ID
label (string) - Display label
options (table) - Optional styling/state options
Returns:
string - HTML for toggle widget
-- Render verse content
renderVerseContent(data)
Parameters:
data (table) - Segment data with SegmentSource, SourceTranslit
Returns:
string - HTML for source text and transliteration
-- Render source header
renderSourceHeader(frame, label, transMemNum, showToggles)
Parameters:
frame - MediaWiki frame
label (string) - Source version label
transMemNum (string) - Translation memory number
showToggles (boolean) - Whether to include toggle controls
Returns:
string - HTML header
-- Render version column
renderVersionColumn(frame, args)
Parameters:
frame - MediaWiki frame
args (table) - Arguments for VersionContent template
Returns:
string - Template expansion result
-- Render translations box
renderTranslationsBox(frame, translations, sourceLabel, transMemNum)
Parameters:
frame - MediaWiki frame
translations (array) - Processed translation data
sourceLabel (string) - Source version label
transMemNum (string) - Translation memory number
Returns:
string - HTML for scrollable translations container
Data Structures[edit]
getAllVerseData() Return Format[edit]
{
bySource = {
['001-Tsadra-BCA-Root-Padmakara'] = {
segments = {
{
Page = 'Translation_Memories/...',
TransMemID = '001-Tsadra-BCA-Root-Padmakara',
TransMemNum = '001',
SegmentSource = 'བྱང་ཆུབ་སེམས་དཔའ་...',
SourceTranslit = 'byang chub sems dpa\'...',
SegmentTranslation = 'Homage to the bodhisattvas...',
SegmentOrder = '1',
RootTextChapterNum = '1',
RootTextVerseNum = '1',
Flag = nil,
Spanish = nil
},
-- More segments...
},
metadata = {
TransMemID = '001-Tsadra-BCA-Root-Padmakara',
TransMemNum = '001',
SourceVersionLabel = 'Padmakara Tibetan',
TranslationWikiPage = 'Books/...',
SourceWikiPage = 'Texts/...',
ShortTitle = 'Padmakara 2006',
RootSourceTransMem = '001-Tsadra-BCA-Root-Padmakara'
}
},
['009-Tsadra-BCA-Root-Gomez'] = { ... },
-- More sources...
},
rootSources = {
['001-Tsadra-BCA-Root-Padmakara'] = { metadata },
['009-Tsadra-BCA-Root-Gomez'] = { metadata },
-- Only root sources
},
translations = {
['001-Tsadra-BCA-Root-Padmakara'] = {
['002-Other-Translation'] = { segments, metadata },
['003-Another-Translation'] = { segments, metadata },
-- All translations based on this root source
},
-- More root sources...
}
}
processTranslations() Return Format[edit]
{
{
id = '002-Other-Translation',
content = {'<p>Translation text...</p>', '<p>More text...</p>'},
source = 'Books/Title',
shortTitle = 'Publisher Year',
year = '2008',
page = 'Translation_Memories/Page'
},
-- More translations, sorted by year
}
Performance Optimization Tips[edit]
Query Optimization[edit]
-- GOOD: One query with all needed properties
local query = [[
[[Category:X]][[Property::Y]]
|?PropA|?PropB|?PropC|?PropD
|limit=1000
]]
-- BAD: Multiple queries
for each item do
local q1 = [[[[Item]]|?PropA]]
local q2 = [[[[Item]]|?PropB]]
end
String Building[edit]
-- GOOD: Build array, concat once
local parts = {'<div>', content, '</div>'}
return table.concat(parts)
-- BAD: Repeated concatenation
local str = '<div>'
str = str .. content
str = str .. '</div>'
return str
Memory Management[edit]
-- GOOD: Local variables only
function getData()
local result = query()
return result
end
-- BAD: Module-level state
local cache = {}
function getData()
cache[key] = query()
return cache[key]
end
Testing[edit]
Unit Testing Data Functions[edit]
-- Test getAllVerseData
local data = require('Module:GetRootVerses/Data')
local result = data.getAllVerseData('1.1')
assert(result.bySource['001-Tsadra-BCA-Root-Padmakara'],
'Should have Tibetan source')
assert(#result.bySource['001-Tsadra-BCA-Root-Padmakara'].segments > 0,
'Should have segments')
Integration Testing[edit]
<!-- Test on sandbox page -->
Test verse 1.1:
{{#invoke:GetRootVerses/sandbox|main|verse=1.1}}
Test missing verse:
{{#invoke:GetRootVerses/sandbox|main|verse=999.999}}
Test SimpleVerse:
{{#invoke:GetRootVerses/sandbox|SimpleVerse|verse=1.1|sources=001-Tsadra-BCA-Root-Padmakara}}Debugging[edit]
Enable Debug Output[edit]
-- Temporary debug code
function p.main(frame)
local verse = pargs.verse
local allData = dataHelper.getAllVerseData(verse)
-- Debug: show what we got
local debug = mw.text.jsonEncode(allData.rootSources)
return '<pre>' .. debug .. '</pre>'
end
Common Issues[edit]
No data returned:
-- Check query results
local results = smw.ask(query)
if not results then
return 'Query returned nil'
end
if #results == 0 then
return 'Query returned empty table'
end
Memory errors:
-- Check for module-level variables
-- These persist across pages:
local persistentData = {} -- BAD
-- These don't:
function getData()
local localData = {} -- GOOD
end
Extending the Module[edit]
Adding a New Source[edit]
No code changes needed! The module automatically includes any source that:
- Is categorized as Translation Memory
- Has
RootTextVerseNumPadmaKproperty set - Has
SegmentFormatset to "Root" - Has
RootSourceTransMempointing to itself (for root sources)
Adding a New Display Feature[edit]
1. Add rendering function to Display module:
-- In Module:GetRootVerses/Display
function p.renderNewFeature(data, options)
local parts = {'<div class="new-feature">'}
-- Build HTML
parts[#parts + 1] = '</div>'
return table.concat(parts)
end
2. Call from main module:
-- In Module:GetRootVerses
local newFeature = displayHelper.renderNewFeature(data, {opt = 'value'})
parts[#parts + 1] = newFeature
Adding a New Data Source[edit]
To fetch from a new data source (beyond SMW):
-- In Module:GetRootVerses/Data
function p.getExternalData(identifier)
local data = mw.ext.externalData.getExternalData{
url = 'https://example.org/api?id=' .. identifier,
data = { result = 'Result' },
format = 'JSON'
}
return data.result
end
Maintenance Procedures[edit]
Updating After MediaWiki Upgrade[edit]
- Test basic functionality
- Check Scribunto version compatibility
- Verify SMW query syntax still works
- Test template expansions
- Monitor performance
Optimizing Queries[edit]
If queries become slow:
-- Add index hints
[[Category:Translation Memories]] -- Indexed
[[RootTextVerseNumPadmaK::1.1]] -- Should be indexed
[[SegmentFormat::Root]] -- Should be indexed
-- Reduce limit if possible
|limit=1000 -- Try 500 if data allows
-- Request only needed properties
|?Prop1|?Prop2 -- Not |?Property::*
Code Style Guidelines[edit]
- Use descriptive variable names
- Comment complex logic
- Keep functions under 50 lines
- Prefer local variables
- Use table.concat for string building
- Validate input parameters
- Return early on errors
- Use helper functions for repeated code
Related Documentation[edit]
- mw:Extension:Scribunto/Lua reference manual
- mw:Extension:Semantic MediaWiki/Ask query
- mw:Help:Extension:ParserFunctions
- Module:GetData - Related data module
-- Module:GetRootVerses (Optimized for minimal queries)
-- Main entry point for verse display functionality
local p = {}
-- Load helper modules
local dataHelper = require('Module:GetRootVerses/Data')
local displayHelper = require('Module:GetRootVerses/Display')
-- Frequently used functions
local smw = mw.smw
local getCurrentTitle = mw.title.getCurrentTitle
-- Store semantic properties for a verse from translation memories
function p.set(frame)
if not smw then
return "mw.smw module not found"
end
local verse = getCurrentTitle().subpageText
-- Get ALL data in one query
local allData = dataHelper.getAllVerseData(verse)
-- Get Tibetan data
local tibetanData = allData.bySource['001-Tsadra-BCA-Root-Padmakara']
if not tibetanData or #tibetanData.segments == 0 then
return ''
end
local data = tibetanData.segments[1]
-- Build data store for SMW
local dataStore = {
'SegmentSource=' .. (data.SegmentSource or ''),
'SourceTranslit=' .. (data.SourceTranslit or ''),
'SegmentTranslation=' .. (data.SegmentTranslation or ''),
'TransMemID=' .. (data.TransMemID or ''),
'TransMemPage=' .. (data.Page or ''),
'SegmentOrder=' .. (data.SegmentOrder or ''),
'RootTextChapVerseNum=' .. (data.RootTextChapVerseNum or ''),
'RootTextChapterNum=' .. (data.RootTextChapterNum or ''),
'RootTextVerseNum=' .. (data.RootTextVerseNum or ''),
'+sep=;',
'SegmentFormat=Root',
'TibetanChanted='
}
-- Add Sanskrit data if available
local sanskritData = allData.bySource['009-Tsadra-BCA-Root-Gomez']
if sanskritData and #sanskritData.segments > 0 then
local skt = sanskritData.segments[1]
dataStore[#dataStore + 1] = 'SanskritSource=' .. (skt.SegmentSource or '')
dataStore[#dataStore + 1] = 'SanskritTranslit=' .. (skt.SourceTranslit or '')
dataStore[#dataStore + 1] = 'SanskritTranslation=' .. (skt.SegmentTranslation or '')
end
-- Add French data if available
local frenchData = allData.bySource['004-Tsadra-BCA-Root-Padmakara-French']
if frenchData and #frenchData.segments > 0 then
dataStore[#dataStore + 1] = 'FrenchTranslation=' .. (frenchData.segments[1].SegmentTranslation or '')
end
-- Add Spanish data if available
local spanishData = allData.bySource['021-Tsadra-BCA-Padmakara-Spanish']
if spanishData and #spanishData.segments > 0 then
dataStore[#dataStore + 1] = 'Spanish=' .. (spanishData.segments[1].SegmentTranslation or '')
end
-- SEO: Build dynamic metadata properties
local chapVerseNum = data.RootTextChapVerseNum or verse
-- Title: use first line of Padmakara English translation as the verse theme
local englishFirstLine = (data.SegmentTranslation or ''):match('^([^\n]+)') or ''
-- Strip inline footnotes: superscript numbers followed by footnote text (e.g. "bliss,25‟Those who...")
-- Footnotes appear as one or more digits immediately followed by a non-space char mid-sentence
englishFirstLine = englishFirstLine:gsub('%d+[‟"\'].*$', '')
-- Strip any remaining wikitext markup for clean title
englishFirstLine = englishFirstLine:gsub('%[%[.-%|(.-)%]%]', '%1'):gsub('%[%[(.-)%]%]', '%1'):gsub("'''?", '')
-- Strip complete HTML tags (e.g. <span>...</span>), then any remaining unclosed tag fragments
englishFirstLine = englishFirstLine:gsub('<[^>]->[^<]*</[^>]+>', ''):gsub('<[^>]*>?.*$', '')
englishFirstLine = englishFirstLine:match('^(.-)%s*$') or englishFirstLine -- trim trailing whitespace
englishFirstLine = englishFirstLine:match('^(.-)%s*[,;]?%s*$') or englishFirstLine -- trim trailing comma/semicolon
-- Collect translator ShortTitles from all present root sources
local translators = {}
local translatorSeen = {}
for _, sourceInfo in pairs(allData.rootSources) do
local st = sourceInfo.ShortTitle or ''
if st ~= '' and not translatorSeen[st] then
translators[#translators + 1] = st
translatorSeen[st] = true
end
end
-- Also include non-root translation ShortTitles
for _, transGroup in pairs(allData.translations) do
for _, transData in pairs(transGroup) do
local st = transData.metadata.ShortTitle or ''
if st ~= '' and not translatorSeen[st] then
translators[#translators + 1] = st
translatorSeen[st] = true
end
end
end
-- Collect commentary author labels: query all commentary basepages,
-- then check each one for segments on this verse (N+1, but N is only 5-12)
local commentaryAuthors = {}
local commBasepages = mw.smw.ask(
'[[Category:Translation Memory Basepages]][[Translation Memory:+]][[TransMemType::Commentary]]' ..
'|?TransMemID|?AuthorLabel|?SourceVersionLabel|limit=50'
)
if commBasepages then
for _, comm in ipairs(commBasepages) do
local label = comm.SourceVersionLabel or comm.AuthorLabel
if comm.TransMemID and label then
local hasSegment = mw.smw.ask(
'[[TransMemID::' .. comm.TransMemID .. ']]' ..
'[[RootTextChapVerseNum::' .. mw.uri.encode(verse) .. ']]' ..
'[[SegmentFormat::!Deprecated]]|limit=1'
)
if hasSegment and #hasSegment > 0 then
commentaryAuthors[#commentaryAuthors + 1] = label
end
end
end
end
-- Build AllTopics: verse identifiers + static terms + translators + commentary authors + language keywords
local topicParts = {
'BCA ' .. chapVerseNum,
'Bodhicaryāvatāra ' .. chapVerseNum,
'Śāntideva',
'Shantideva',
'Buddhist Translation Memory',
'Way of the Bodhisattva',
'Bilingual Buddhist Text'
}
for _, t in ipairs(translators) do
topicParts[#topicParts + 1] = t
end
for _, a in ipairs(commentaryAuthors) do
topicParts[#topicParts + 1] = a
end
if allData.bySource['009-Tsadra-BCA-Root-Gomez'] then
topicParts[#topicParts + 1] = 'Sanskrit Buddhist Poetry'
end
if allData.bySource['004-Tsadra-BCA-Root-Padmakara-French'] then
topicParts[#topicParts + 1] = 'French Buddhist Translation'
end
if allData.bySource['021-Tsadra-BCA-Padmakara-Spanish'] or
(allData.translations['001-Tsadra-BCA-Root-Padmakara'] and
allData.translations['001-Tsadra-BCA-Root-Padmakara']['001-Spanish']) then
topicParts[#topicParts + 1] = 'Spanish Buddhist Translation'
end
-- Build Description
local translatorStr = #translators > 0 and table.concat(translators, ', ') or 'multiple translators'
local commStr = #commentaryAuthors > 0 and (' Commentaries by ' .. table.concat(commentaryAuthors, ', ') .. '.') or ''
local description = 'Study hub for Bodhicaryāvatāra Verse ' .. chapVerseNum ..
'. Features the original Tibetan and Sanskrit with translations by ' ..
translatorStr .. '.' .. commStr
-- Set SEO properties
dataStore[#dataStore + 1] = 'Title=BCA Verse ' .. chapVerseNum .. ': ' .. englishFirstLine .. ' | Bodhicaryāvatāra'
dataStore[#dataStore + 1] = 'Description=' .. description
dataStore[#dataStore + 1] = 'AllTopics=' .. table.concat(topicParts, '; ')
local success = smw.set(dataStore)
return success == true and '' or 'An error occurred: ' .. (success.error or 'unknown error')
end
-- Generate toggle controls for different source versions
function p.getSourceToggles(frame)
local pargs = frame:getParent().args
local verse = pargs.verse or getCurrentTitle().subpageText
-- Get all data in one query
local allData = dataHelper.getAllVerseData(verse)
local outputs = {}
for tmid, metadata in pairs(allData.rootSources) do
if metadata.TransMemNum and metadata.SourceVersionLabel then
outputs[#outputs + 1] = displayHelper.createToggle(
frame,
'source-' .. metadata.TransMemNum,
metadata.SourceVersionLabel
)
end
end
return table.concat(outputs)
end
-- Main verse display function
function p.main(frame)
local pargs = frame:getParent().args
local verse = pargs.verse or getCurrentTitle().subpageText
-- SINGLE QUERY to get all data
local allData = dataHelper.getAllVerseData(verse)
if not allData.rootSources or not next(allData.rootSources) then
return ''
end
local output = {'<div class="row">'}
-- Sort root sources by TransMemNum
local sortedSources = {}
for tmid, metadata in pairs(allData.rootSources) do
sortedSources[#sortedSources + 1] = {tmid = tmid, metadata = metadata}
end
table.sort(sortedSources, function(a, b)
return (a.metadata.SourceSortOrder or '') < (b.metadata.SourceSortOrder or '')
end)
for _, sourceInfo in ipairs(sortedSources) do
local tmid = sourceInfo.tmid
local sourceData = allData.bySource[tmid]
if sourceData and #sourceData.segments > 0 then
local section = renderSourceSection(frame, verse, sourceData, allData.translations[tmid])
if section then
output[#output + 1] = section
end
end
end
output[#output + 1] = '</div>'
return table.concat(output)
end
-- Helper: Render a complete source section
function renderSourceSection(frame, verse, sourceData, translationsData)
local metadata = sourceData.metadata
local processed = dataHelper.processVerseSegments(sourceData.segments)
if not processed then
return nil
end
local parts = {
'<div id="source-', metadata.TransMemNum,
'" class="toggleable col-xl-6 col-xxxl-4 position-relative mb-2">'
}
-- Source header with toggles
parts[#parts + 1] = displayHelper.renderSourceHeader(
frame,
metadata.SourceVersionLabel,
metadata.TransMemNum,
true
)
parts[#parts + 1] = '<div class="row">'
-- Build source content
local sourceContent = {}
local translationContent = {}
for i, segment in ipairs(processed.segments) do
sourceContent[#sourceContent + 1] = displayHelper.renderVerseContent(segment)
local showAltVerse = (metadata.TransMemNum == '005')
sourceContent[#sourceContent + 1] = displayHelper.renderVerseInfo(segment, {
showAltVerse = showAltVerse
})
if segment.SegmentTranslation then
translationContent[#translationContent + 1] = '<p>'
translationContent[#translationContent + 1] = segment.SegmentTranslation
translationContent[#translationContent + 1] = '</p>'
end
end
-- Add audio players (only if needed)
if processed.chapterNum and metadata.TransMemNum == '001' then
for verseNum in pairs(processed.verseNums) do
local audioFile = dataHelper.getAudioTrack(processed.chapterNum, verseNum, 'tibetan', metadata.TransMemNum)
if audioFile then
sourceContent[#sourceContent + 1] = displayHelper.renderAudioVerse(
frame, audioFile, metadata.TransMemNum, verseNum,
processed.segments[1].SegmentOrder or '1'
)
end
end
elseif processed.chapterNum and metadata.TransMemNum == '009' then
for verseNum in pairs(processed.verseNums) do
local audioFile = dataHelper.getAudioTrack(processed.chapterNum, verseNum, 'sanskrit', metadata.TransMemNum)
if audioFile then
sourceContent[#sourceContent + 1] = displayHelper.renderAudioVerse(
frame, audioFile, metadata.TransMemNum, verseNum,
processed.segments[1].SegmentOrder or '1'
)
end
end
end
-- Handle special cases for translation content
local translationText = table.concat(translationContent)
local sourceWidth, transWidth = 'col-sm-5', 'col-sm-7'
if metadata.TransMemNum == '009' and translationText == '' then
translationText = '<em>Translation Currently Unavailable</em>'
elseif metadata.TransMemNum == '017' and translationText == '' then
translationText = ''
sourceWidth, transWidth = 'col-12', 'col-12'
end
-- Render columns
parts[#parts + 1] = displayHelper.renderVersionColumn(frame, {
class = 'source-column ' .. sourceWidth .. ' pr-sm-0 mb-2',
content = table.concat(sourceContent),
fontSizeAdjust = '160',
flag = table.concat(processed.flags, ';'),
id = 'sourceLang-' .. metadata.TransMemNum
})
parts[#parts + 1] = displayHelper.renderVersionColumn(frame, {
class = 'translation-column ' .. transWidth .. ' mb-2',
content = translationText,
source = metadata.TranslationWikiPage or metadata.SourceWikiPage,
id = 'transLang-' .. metadata.TransMemNum,
shorttitle = metadata.ShortTitle
})
parts[#parts + 1] = '</div>'
-- Other translations section
if translationsData and next(translationsData) then
local processedTranslations = dataHelper.processTranslations(translationsData)
if #processedTranslations > 0 then
local isChecked = (metadata.TransMemNum == "001")
parts[#parts + 1] = displayHelper.renderTranslationsToggle(
frame, metadata.TransMemNum, isChecked
)
parts[#parts + 1] = displayHelper.renderTranslationsBox(
frame,
processedTranslations,
metadata.SourceVersionLabel,
metadata.TransMemNum
)
end
end
parts[#parts + 1] = '</div>'
return table.concat(parts)
end
-- Simplified verse display
function p.SimpleVerse(frame)
local pargs = frame:getParent().args
local verse = pargs.verse
local sources = pargs.sources
if not verse or not sources then
return ''
end
-- SINGLE QUERY to get all data
local allData = dataHelper.getAllVerseData(verse)
local output = {'<div class="mx-2 mx-sm-0">'}
for source in string.gmatch(sources, "([^;]+)") do
local sourceData = allData.bySource[source]
if sourceData and #sourceData.segments > 0 then
local section = renderSimpleSourceSection(frame, verse, sourceData, allData.translations[source])
if section then
output[#output + 1] = section
end
end
end
output[#output + 1] = '</div>'
return table.concat(output)
end
-- Helper: Render simplified source section
function renderSimpleSourceSection(frame, verse, sourceData, translationsData)
local metadata = sourceData.metadata
local processed = dataHelper.processVerseSegments(sourceData.segments)
if not processed then
return nil
end
local parts = {'<div class="position-relative mb-4">'}
-- Header
parts[#parts + 1] = '<div class="h4 mt-2 mb-3">'
parts[#parts + 1] = metadata.SourceVersionLabel
if metadata.TransMemNum == '005' and processed.segments[1].RootTextChapVerseNum then
parts[#parts + 1] = ' <span class="text-70 text-muted">(v. '
parts[#parts + 1] = processed.segments[1].RootTextChapVerseNum
parts[#parts + 1] = ')</span>'
end
parts[#parts + 1] = '</div><div class="row">'
-- Build content
local sourceContent = {}
local translationContent = {}
for _, segment in ipairs(processed.segments) do
sourceContent[#sourceContent + 1] = displayHelper.renderVerseContent(segment)
if segment.SegmentTranslation then
translationContent[#translationContent + 1] = '<p>'
translationContent[#translationContent + 1] = segment.SegmentTranslation
translationContent[#translationContent + 1] = '</p>'
end
end
-- Columns
parts[#parts + 1] = displayHelper.renderVersionColumn(frame, {
class = 'col-sm-5 pr-sm-0 mb-2',
content = table.concat(sourceContent),
fontSizeAdjust = '140',
flag = table.concat(processed.flags, ';')
})
parts[#parts + 1] = displayHelper.renderVersionColumn(frame, {
class = 'col-sm-7 mb-2',
content = table.concat(translationContent),
source = metadata.TranslationWikiPage,
shorttitle = metadata.ShortTitle
})
parts[#parts + 1] = '</div>'
-- Other translations
if translationsData and next(translationsData) then
local processedTranslations = dataHelper.processTranslations(translationsData)
if #processedTranslations > 0 then
parts[#parts + 1] = displayHelper.renderSimpleTranslationsBox(
frame,
processedTranslations,
metadata.SourceVersionLabel
)
end
end
parts[#parts + 1] = '</div>'
return table.concat(parts)
end
return p