Merge pull request #9 from nmemmert/claude/greek-suggest-cross-device-sync

Fix Greek suggest: use local macula-greek concordance instead of brok…
This commit is contained in:
nmemmert authored and GitHub committed 2026-05-28 12:47:15 -04:00
commit 337006c72d
2 files changed
+32 -25

No files matched your search

+1
View File
@@ -0,0 +1 @@
{"MAT":{"1":{"1":["G976","G1078","G2424","G5547","G5207","G1138","G11"],"2":["G11","G1080","G3588","G2464","G1161","G2384","G2455","G2532","G80","G846"],"3":["G2455","G1161","G1080","G3588","G5329","G2532","G2196","G1537","G2283","G2074","G689"],"4":["G689","G1161","G1080","G3588","G284","G3476","G4533"],"5":["G4533","G1161","G1080","G3588","G1003","G1537","G4477","G5601","G4503","G2421"],"6":["G2421","G1161","G1080","G3588","G1138","G935","G4672","G1537","G3774"],"7":["G4672","G1161","G1080","G3588","G4497","G7","G760"],"8":["G760","G1161","G1080","G3588","G2498","G2496","G3604"],"9":["G3604","G1161","G1080","G3588","G2488","G881","G1478"],"10":["G1478","G1161","G1080","G3588","G3128","G300","G2502"],"11":["G2502","G1161","G1080","G3588","G2423","G2532","G80","G846","G1909","G3350","G897"],"12":["G3326","G1161","G3588","G3350","G897","G2423","G1080","G4528","G2216"],"13":["G2216","G1161","G1080","G3588","G10","G1662","G107"],"14":["G107","G1161","G1080","G3588","G4524","G885","G1664"],"15":["G1664","G1161","G1080","G3588","G1648","G3157","G2384"],"16":["G2384","G1161","G1080","G3588","G2501","G435","G3137","G1537","G3739","G2424","G3004","G5547"],"17":["G3956","G3767","G3588","G1074","G575","G11","G2193","G1138","G1180","G2532","G3350","G897","G5547"],"18":["G3588","G1161","G2424","G5547","G1078","G3779","G2258","G3423","G3384","G846","G3137","G2501","G4250","G2228","G4905","G848","G2147","G1722","G1064","G2192","G1537","G4151","G40"],"19":["G2501","G1161","G3588","G435","G846","G1342","G5607","G2532","G3361","G2309","G848","G1165","G1014","G2977","G630"],"20":["G5023","G1161","G846","G1760","G2400","G32","G2962","G2596","G3677","G5316","G3004","G2501","G5207","G1138","G3361","G5399","G3880","G3137","G3588","G1135","G4675","G1063","G1722","G1080","G1537","G4151","G2076","G40"],"21":["G5088","G1161","G5207","G2532","G2564","G3588","G3686","G846","G2424","G1063","G4982","G2992","G575","G266"],"22":["G5124","G1161","G3650","G1096","G2443","G4137","G3588","G3004","G5259","G2962","G1223","G4396"],"23":["G2400","G3588","G3933","G1722","G1064","G2192","G2532","G5088","G5207","G2564","G3686","G846","G1694","G3739","G2076","G3177","G3326","G2257","G2316"],"24":["G1453","G1161","G3588","G2501","G575","G5258","G4160","G5613","G4367","G846","G32","G2962","G2532","G3880","G1135"],"25":["G2532","G3756","G1097","G848","G2193","G3739","G5088","G5207","G2564","G3588","G3686","G846","G2424"]},"2":{"1":["G3588","G1161","G2424","G1080","G1722","G965","G2449","G2250","G2264","G935","G2400","G3097","G575","G395","G3854","G1519","G2414"],"2":["G3004","G4226","G2076","G3588","G5088","G935","G2453","G3708","G1063","G846","G792","G1722","G395","G2532","G2064","G4352"],"3":["G191","G1161","G3588","G935","G2264","G5015","G2532","G3956","G2414","G3326","G846"],"4":["G2532","G4863","G3956","G3588","G749","G1122","G2992","G4441","G3844","G846","G4226","G5547","G1080"],"5":["G3588","G1161","G2036","G846","G1722","G965","G2449","G3779","G1063","G1125","G1223","G4396"],"6":["G2532","G4771","G965","G1093","G2448","G3760","G1646","G1488","G1722","G3588","G2232","G1537","G4675","G1063","G1831","G2233","G3748","G4165","G2992","G3450","G2474"],"7":["G5119","G2264","G2977","G2564","G3588","G3097","G198","G3844","G846","G5550","G5316","G792"],"8":["G2532","G3992","G848","G1519","G965","G2036","G4198","G1833","G199","G4012","G3588","G3813","G1875","G1161","G2147","G518","G3427","G3704","G2504","G2064","G4352","G846"],"9":["G3588","G1161","G191","G935","G4198","G2532","G2400","G792","G3739","G3708","G1722","G395","G4254","G848","G2193","G2064","G2476","G1883","G3757","G2258","G3813"],"10":["G3708","G1161","G3588","G792","G5463","G5479","G3173","G4970"],"11":["G2532","G2064","G1519","G3588","G3614","G3708","G3813","G3326","G3137","G3384","G846","G4098","G4352","G455","G2344","G4374","G1435","G5557","G3030","G4666"],"12":["G2532","G5537","G2596","G3677","G3361","G344","G4314","G2264","G1223","G243","G3598","G402","G1519","G3588","G5561","G846"],"13":["G402","G1161","G846","G2400","G32","G2962","G5316","G2596","G3677","G3588","G2501","G3004","G1453","G3880","G3813","G2532","G3384","G5343","G1519","G125","G2468","G1563","G2193","G302","G2036","G4671","G3195","G1063","G2264","G2212","G622"],"14":["G3588","G1161","G1453","G3880","G3813","G2532","G3384","G846","G3571","G402","G1519","G125"],"15":["G2532","G2258","G1563","G2193","G3588","G5054","G2264","G2443","G4137","G3004","G5259","G2962","G1223","G4396","G1537","G125","G2564","G5207","G3450"],"16":["G5119","G2264","G3708","G3754","G1702","G5259","G3588","G3097","G2373","G3029","G2532","G649","G337","G3956","G3816","G1722","G965","G3725","G846","G575","G1332","G2736","G2596","G5550","G3739","G198","G3844"],"17":["G5119","G4137","G3588","G3004","G1223","G2408","G4396"],"18":["G5456","G1722","G4471","G191","G2805","G2532","G3602","G4183","G4478","G2799","G3588","G5043","G846","G3756","G2309","G3870","G3754","G1526"],"19":["G5053","G1161","G3588","G2264","G2400","G32","G2962","G5316","G2596","G3677","G250Line truncated
+31 -25
View File
@@ -910,6 +910,23 @@ useEffect(() => {
}));
};
// Cache for the NT Strong's concordance (verse → Strong's numbers), loaded once
const _concordanceRef = useRef(null);
const _concordanceLoadingRef = useRef(null);
const loadNtConcordance = async () => {
if (_concordanceRef.current) return _concordanceRef.current;
if (_concordanceLoadingRef.current) return _concordanceLoadingRef.current;
_concordanceLoadingRef.current = fetch('/nt-strongs-concordance.json')
.then((res) => res.json())
.then((data) => {
_concordanceRef.current = data;
_concordanceLoadingRef.current = null;
return data;
});
return _concordanceLoadingRef.current;
};
const suggestGreekWordsForChunk = async (chunkId) => {
const chunk = allChunks.find((c) => c.id === chunkId);
const chapter = project?.chapters.find((ch) =>
@@ -923,47 +940,36 @@ useEffect(() => {
}
setSuggestingGreekForChunkId(chunkId);
try {
const res = await fetch(
`https://bible.helloao.org/api/SBLGNT/${chapter.bookAbbrev}/${chapter.chapter}.json`
);
if (!res.ok) throw new Error('Could not fetch interlinear data.');
const data = await res.json();
const verses = Array.isArray(data?.verses)
? data.verses
: (data?.chapter?.content ?? []).filter((item) => item?.type === 'verse');
const strongsInRange = new Map();
for (const verse of verses) {
const verseNum = verse.number ?? verse.verse;
if (verseNum < chunk.startVerse || verseNum > chunk.endVerse) continue;
const words = Array.isArray(verse.content) ? verse.content : [];
for (const word of words) {
const strongs = word?.strongs ?? word?.strong;
const text = word?.text ?? word?.greek ?? '';
const translit = word?.transliteration ?? word?.translit ?? '';
if (!strongs || !/^G\d+$/i.test(strongs)) continue;
const key = strongs.toUpperCase();
if (!strongsInRange.has(key)) strongsInRange.set(key, { strongs: key, text, translit });
}
const [concordance, dict] = await Promise.all([loadNtConcordance(), loadGreekDict()]);
const bookData = concordance[chapter.bookAbbrev] ?? {};
const chapterData = bookData[chapter.chapter] ?? {};
// Collect unique Strong's numbers across the verse range
const strongsInRange = new Set();
for (let v = chunk.startVerse; v <= chunk.endVerse; v++) {
const verseStrongs = chapterData[String(v)] ?? [];
for (const s of verseStrongs) strongsInRange.add(s);
}
if (strongsInRange.size === 0) {
setStatusMessage("No Strong's data found for this passage.");
window.setTimeout(() => setStatusMessage(''), 3000);
return;
}
const dict = await loadGreekDict();
const existingNumbers = new Set(
chunk.greekWords.map((w) => w.strongNumber.toUpperCase()).filter(Boolean)
);
const newWords = [];
for (const [strongKey, meta] of strongsInRange) {
for (const strongKey of strongsInRange) {
if (existingNumbers.has(strongKey)) continue;
const entry = dict[strongKey];
newWords.push({
id: makeId(),
query: strongKey,
strongNumber: strongKey,
lexeme: entry?.lemma ?? meta.text ?? '',
transliteration: entry?.translit ?? meta.translit ?? '',
lexeme: entry?.lemma ?? '',
transliteration: entry?.translit ?? '',
partOfSpeech: '',
shortDefinition: entry?.kjv_def ?? 'No definition found.',
definitionHtml: entry ? buildGreekDefinitionHtml(strongKey, entry) : '',