From 48b3790cd228eeeeb357807a2f19efb96716dbb3 Mon Sep 17 00:00:00 2001 From: nmemmert Date: Thu, 28 May 2026 07:49:55 -0400 Subject: [PATCH] Fix Greek word lookup: replace broken Bolls BDAG with OpenScriptures Strong's dictionary The Bolls.life BDAG endpoint (/dictionary-definition/BDAG/) now returns empty arrays for all queries, and the search endpoint returns 404. Switched Greek lookups to the OpenScriptures Strong's Greek Dictionary served via jsDelivr CDN. The ~1.2 MB dictionary is fetched once on first lookup and cached in a useRef for the session, so subsequent lookups are instant. Hebrew lookups (Bolls BDBT) are unchanged and still working. Supports Strong's number lookup (G4102, 4102) and English word search (faith, apostle), returning lemma, transliteration, short definition, and an extended definition with derivation and KJV keyword list. Co-Authored-By: Claude Sonnet 4.6 --- .claude/launch.json | 3 +- src/App.jsx | 91 +++++++++++++++++++++++++++++++++------------ 2 files changed, 69 insertions(+), 25 deletions(-) diff --git a/.claude/launch.json b/.claude/launch.json index 026950b..152f91a 100644 --- a/.claude/launch.json +++ b/.claude/launch.json @@ -5,7 +5,8 @@ "name": "study-app", "runtimeExecutable": "npm", "runtimeArgs": ["run", "dev"], - "port": 5173 + "port": 5173, + "autoPort": false } ] } diff --git a/src/App.jsx b/src/App.jsx index 8b1b1d1..ed66f67 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -892,36 +892,79 @@ const App = () => { ]; }; - const fetchBollsDefinition = async (query) => { - const isGreek = isGreekStrongNumber(query); - const isHebrew = isHebrewStrongNumber(query); + // Module-level cache for the Greek Strong's dictionary (loaded once, ~1.2 MB) + const _greekDictRef = useRef(null); + const _greekDictLoadingRef = useRef(null); - if (isGreek || isHebrew) { - // The bolls.life API uses bare numbers in the URL (no G/H prefix). - // We filter returned results by topic prefix to get the right language. - const bare = query.trim().replace(/^[GgHh]/, ''); - const response = await fetch(`https://bolls.life/dictionary-definition/BDBT/${encodeURIComponent(bare)}/`); + const loadGreekDict = async () => { + if (_greekDictRef.current) return _greekDictRef.current; + if (_greekDictLoadingRef.current) return _greekDictLoadingRef.current; + _greekDictLoadingRef.current = fetch( + 'https://cdn.jsdelivr.net/gh/openscriptures/strongs@master/greek/strongs-greek-dictionary.js', + ) + .then((res) => res.text()) + .then((text) => { + const start = text.indexOf('{'); + const end = text.lastIndexOf('}') + 1; + const dict = JSON.parse(text.slice(start, end)); + _greekDictRef.current = dict; + _greekDictLoadingRef.current = null; + return dict; + }); + return _greekDictLoadingRef.current; + }; + + const buildGreekDefinitionHtml = (key, entry) => { + let html = ''; + if (entry.strongs_def) html += `

${entry.strongs_def.trim()}

`; + if (entry.derivation) html += `

Derivation: ${entry.derivation}

`; + if (entry.kjv_def) html += `

KJV uses: ${entry.kjv_def}

`; + return html; + }; + + const fetchBollsDefinition = async (query) => { + const encoded = encodeURIComponent(query.trim()); + + if (isHebrewStrongNumber(query)) { + const response = await fetch(`https://bolls.life/dictionary-definition/BDBT/${encoded}/`); if (!response.ok) return null; let defs; try { defs = await response.json(); } catch { return null; } - if (!Array.isArray(defs) || defs.length === 0) return null; - const prefix = isGreek ? 'G' : 'H'; - const filtered = defs.filter((d) => String(d.topic ?? '').toUpperCase().startsWith(prefix)); - return filtered.length > 0 ? filtered : defs; + return Array.isArray(defs) && defs.length > 0 ? defs : null; } - // English word — full-text search, filter to Greek entries only - const encoded = encodeURIComponent(query.trim()); - try { - const searchResponse = await fetch(`https://bolls.life/search-dictionaries/BDBT/${encoded}/`); - if (searchResponse.ok) { - const results = await searchResponse.json(); - const greekResults = Array.isArray(results) - ? results.filter((r) => String(r.topic ?? '').startsWith('G')) - : []; - if (greekResults.length > 0) return [greekResults[0]]; - } - } catch { /* fall through */ } + // Greek — look up in the cached OpenScriptures Strong's dictionary. + const dict = await loadGreekDict(); + const key = isGreekStrongNumber(query) ? query.trim().toUpperCase() : null; + + if (key) { + const entry = dict[key]; + if (!entry) return null; + return [{ + topic: key, + lexeme: entry.lemma || '', + transliteration: entry.translit || '', + short_definition: entry.kjv_def || '', + definition: buildGreekDefinitionHtml(key, entry), + }]; + } + + // English word search — scan kjv_def and strongs_def for the query term. + const lowerQ = query.trim().toLowerCase(); + const match = Object.entries(dict).find(([, entry]) => + (entry.kjv_def || '').toLowerCase().split(/[,\s]+/).some((w) => w === lowerQ) || + (entry.strongs_def || '').toLowerCase().includes(lowerQ), + ); + if (match) { + const [matchKey, entry] = match; + return [{ + topic: matchKey, + lexeme: entry.lemma || '', + transliteration: entry.translit || '', + short_definition: entry.kjv_def || '', + definition: buildGreekDefinitionHtml(matchKey, entry), + }]; + } return null; };