From c2d5bd3093daab37ff259962e0e56239bb02264a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BD=D1=82=D0=BE=D0=BD=20=D0=A2=D1=80=D0=BE=D1=88?= =?UTF-8?q?=D0=B8=D0=BD?= Date: Sun, 2 Aug 2026 14:42:34 +0300 Subject: [PATCH] feat: Bible translation preloading with translation picker --- src/core/i18n.ts | 4 ++++ src/core/offline-manager.ts | 47 +++++++++++++++++++++++++++++++++---- src/ui/settings-view.ts | 33 +++++++++++++++++++++++++- 3 files changed, 79 insertions(+), 5 deletions(-) diff --git a/src/core/i18n.ts b/src/core/i18n.ts index 8dc18a0..801502b 100644 --- a/src/core/i18n.ts +++ b/src/core/i18n.ts @@ -494,6 +494,7 @@ export interface Translations { offlineLives: string; offlineCalendar: string; offlineMenaion: string; + offlineBible: string; offlinePreload: string; offlineClearCache: string; offlineCalculating: string; @@ -1041,6 +1042,7 @@ const translations: Record = { offlineLives: 'Lives', offlineCalendar: 'Calendar', offlineMenaion: 'Menaion', + offlineBible: 'Bible', offlinePreload: 'Preload', offlineClearCache: 'Clear cache', offlineCalculating: 'Calculating storage...', @@ -1586,6 +1588,7 @@ const translations: Record = { offlineLives: 'Жития', offlineCalendar: 'Календарь', offlineMenaion: 'Минея', + offlineBible: 'Библия', offlinePreload: 'Загрузить', offlineClearCache: 'Очистить кэш', offlineCalculating: 'Расчёт размера...', @@ -2131,6 +2134,7 @@ const translations: Record = { offlineLives: 'Житїя', offlineCalendar: 'Календарь', offlineMenaion: 'Минїа', + offlineBible: 'Би́блїа', offlinePreload: 'Загрузити', offlineClearCache: 'Очистити кешъ', offlineCalculating: 'Расчётъ размѣра...', diff --git a/src/core/offline-manager.ts b/src/core/offline-manager.ts index 73e8f8f..ed7a6c2 100644 --- a/src/core/offline-manager.ts +++ b/src/core/offline-manager.ts @@ -19,6 +19,7 @@ import { DataCache } from './data-cache'; export interface PreloadOptions { languages: string[]; types: ('lives' | 'bible' | 'calendar' | 'menaion')[]; + bibleTranslations?: string[]; } export interface PreloadProgress { @@ -36,6 +37,15 @@ export interface CacheStats { cachedTypes: string[]; } +/** Bible translation sizes (pre-computed). */ +const BIBLE_SIZES: Record = { + 'en/bible/kjv': '5.1 MB', 'en/bible/brenton': '4.1 MB', 'en/bible/nonkjv': '0.9 MB', + 'ru/bible/synod': '7.3 MB', 'ru/bible/kassian': '1.5 MB', 'ru/bible/yungerov': '1.7 MB', + 'cu/bible/elis': '8.4 MB', 'el/bible/spt': '8.6 MB', 'fr/bible/ls': '5.2 MB', + 'la/bible/vulgate': '4.4 MB', 'ar/bible/svd': '7.9 MB', + 'zh/Hans/bible/cuv': '3.7 MB', 'zh/Hant/bible/cuv': '3.7 MB', +}; + /** Data files to preload per language, grouped by type. */ const DATA_FILES: Record string[]> = { lives: (lang) => { @@ -59,17 +69,35 @@ const DATA_FILES: Record string[]> = { menaion: (lang) => [ `/data/${lang}/menaion-bundle.json`, ], - bible: (lang) => [ - // Bible version index + bible: (_lang) => [ '/data/bible/versions.json', - // Main Bible text files per language - `/data/${lang}/bible/synod.text`, ], }; export class OfflineManager { private static _progress: PreloadProgress | null = null; + /** Get size string for a Bible translation. */ + static getBibleSize(translationPath: string): string { + return BIBLE_SIZES[translationPath] || '?'; + } + + /** Discover all book files for a Bible translation from versions.json. */ + static async getBibleFiles(translationPath: string): Promise { + const files: string[] = []; + try { + const resp = await fetch('/data/bible/versions.json'); + const versions = await resp.json() as any[]; + const ver = versions.find((v: any) => v.id === translationPath); + if (ver && ver.books) { + for (const book of ver.books) { + files.push(`/data/${translationPath}/${book.id}.text`); + } + } + } catch {} + return files; + } + /** Get current preload progress */ static getProgress(): PreloadProgress | null { return OfflineManager._progress; @@ -94,6 +122,17 @@ export class OfflineManager { // Deduplicate const uniqueFiles = [...new Set(filesToFetch)]; + // Add Bible translation files + for (const trans of options.bibleTranslations || []) { + const bibleFiles = await OfflineManager.getBibleFiles(trans); + for (const f of bibleFiles) { + if (!uniqueFiles.includes(f)) uniqueFiles.push(f); + } + } + if (!uniqueFiles.includes('/data/bible/versions.json')) { + uniqueFiles.unshift('/data/bible/versions.json'); + } + const progress: PreloadProgress = { current: 0, total: uniqueFiles.length, diff --git a/src/ui/settings-view.ts b/src/ui/settings-view.ts index 890ff59..bf9403e 100644 --- a/src/ui/settings-view.ts +++ b/src/ui/settings-view.ts @@ -292,7 +292,11 @@ export class SettingsView { + + @@ -409,6 +413,26 @@ export class SettingsView { } }); + // Bible translation sub-list + document.querySelector('.offline-type[value="bible"]')?.addEventListener('change', async (e) => { + const checked = (e.target as HTMLInputElement).checked; + const container = document.getElementById('bible-translations'); + if (!container) return; + container.classList.toggle('hidden', !checked); + if (!checked) { container.innerHTML = ''; return; } + try { + const resp = await fetch('/data/bible/versions.json'); + const versions = await resp.json(); + container.innerHTML = versions.map((v: any) => ` + + `).join(''); + } catch {} + }); + // Offline content - auto-select ru+cu when ru is selected document.querySelectorAll('.offline-lang').forEach(el => { el.addEventListener('change', () => { @@ -438,8 +462,15 @@ export class SettingsView { const progressText = document.getElementById('offline-progress-text'); if (progressDiv) progressDiv.classList.remove('hidden'); + const bibleTrans = Array.from(document.querySelectorAll('.bible-trans:checked')) + .map(el => (el as HTMLInputElement).value); + // Start preloading in background - OfflineManager.preload({ languages: selectedLangs, types: selectedTypes }).then(() => { + OfflineManager.preload({ + languages: selectedLangs, + types: selectedTypes, + bibleTranslations: bibleTrans, + }).then(() => { if (progressBar) progressBar.style.width = '100%'; if (progressText) progressText.textContent = t.settings.offlineDone; OfflineManager.getStats().then(stats => {