feat: Bible translation preloading with translation picker
This commit is contained in:
1 parent
04eaa7058c
commit
c2d5bd3093
3 files changed
+79
-5
No files matched your search
@@ -494,6 +494,7 @@ export interface Translations {
|
|||||||
offlineLives: string;
|
offlineLives: string;
|
||||||
offlineCalendar: string;
|
offlineCalendar: string;
|
||||||
offlineMenaion: string;
|
offlineMenaion: string;
|
||||||
|
offlineBible: string;
|
||||||
offlinePreload: string;
|
offlinePreload: string;
|
||||||
offlineClearCache: string;
|
offlineClearCache: string;
|
||||||
offlineCalculating: string;
|
offlineCalculating: string;
|
||||||
@@ -1041,6 +1042,7 @@ const translations: Record<LanguageCode, Translations> = {
|
|||||||
offlineLives: 'Lives',
|
offlineLives: 'Lives',
|
||||||
offlineCalendar: 'Calendar',
|
offlineCalendar: 'Calendar',
|
||||||
offlineMenaion: 'Menaion',
|
offlineMenaion: 'Menaion',
|
||||||
|
offlineBible: 'Bible',
|
||||||
offlinePreload: 'Preload',
|
offlinePreload: 'Preload',
|
||||||
offlineClearCache: 'Clear cache',
|
offlineClearCache: 'Clear cache',
|
||||||
offlineCalculating: 'Calculating storage...',
|
offlineCalculating: 'Calculating storage...',
|
||||||
@@ -1586,6 +1588,7 @@ const translations: Record<LanguageCode, Translations> = {
|
|||||||
offlineLives: 'Жития',
|
offlineLives: 'Жития',
|
||||||
offlineCalendar: 'Календарь',
|
offlineCalendar: 'Календарь',
|
||||||
offlineMenaion: 'Минея',
|
offlineMenaion: 'Минея',
|
||||||
|
offlineBible: 'Библия',
|
||||||
offlinePreload: 'Загрузить',
|
offlinePreload: 'Загрузить',
|
||||||
offlineClearCache: 'Очистить кэш',
|
offlineClearCache: 'Очистить кэш',
|
||||||
offlineCalculating: 'Расчёт размера...',
|
offlineCalculating: 'Расчёт размера...',
|
||||||
@@ -2131,6 +2134,7 @@ const translations: Record<LanguageCode, Translations> = {
|
|||||||
offlineLives: 'Житїя',
|
offlineLives: 'Житїя',
|
||||||
offlineCalendar: 'Календарь',
|
offlineCalendar: 'Календарь',
|
||||||
offlineMenaion: 'Минїа',
|
offlineMenaion: 'Минїа',
|
||||||
|
offlineBible: 'Би́блїа',
|
||||||
offlinePreload: 'Загрузити',
|
offlinePreload: 'Загрузити',
|
||||||
offlineClearCache: 'Очистити кешъ',
|
offlineClearCache: 'Очистити кешъ',
|
||||||
offlineCalculating: 'Расчётъ размѣра...',
|
offlineCalculating: 'Расчётъ размѣра...',
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import { DataCache } from './data-cache';
|
|||||||
export interface PreloadOptions {
|
export interface PreloadOptions {
|
||||||
languages: string[];
|
languages: string[];
|
||||||
types: ('lives' | 'bible' | 'calendar' | 'menaion')[];
|
types: ('lives' | 'bible' | 'calendar' | 'menaion')[];
|
||||||
|
bibleTranslations?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PreloadProgress {
|
export interface PreloadProgress {
|
||||||
@@ -36,6 +37,15 @@ export interface CacheStats {
|
|||||||
cachedTypes: string[];
|
cachedTypes: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Bible translation sizes (pre-computed). */
|
||||||
|
const BIBLE_SIZES: Record<string, string> = {
|
||||||
|
'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. */
|
/** Data files to preload per language, grouped by type. */
|
||||||
const DATA_FILES: Record<string, (lang: string) => string[]> = {
|
const DATA_FILES: Record<string, (lang: string) => string[]> = {
|
||||||
lives: (lang) => {
|
lives: (lang) => {
|
||||||
@@ -59,17 +69,35 @@ const DATA_FILES: Record<string, (lang: string) => string[]> = {
|
|||||||
menaion: (lang) => [
|
menaion: (lang) => [
|
||||||
`/data/${lang}/menaion-bundle.json`,
|
`/data/${lang}/menaion-bundle.json`,
|
||||||
],
|
],
|
||||||
bible: (lang) => [
|
bible: (_lang) => [
|
||||||
// Bible version index
|
|
||||||
'/data/bible/versions.json',
|
'/data/bible/versions.json',
|
||||||
// Main Bible text files per language
|
|
||||||
`/data/${lang}/bible/synod.text`,
|
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
export class OfflineManager {
|
export class OfflineManager {
|
||||||
private static _progress: PreloadProgress | null = null;
|
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<string[]> {
|
||||||
|
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 */
|
/** Get current preload progress */
|
||||||
static getProgress(): PreloadProgress | null {
|
static getProgress(): PreloadProgress | null {
|
||||||
return OfflineManager._progress;
|
return OfflineManager._progress;
|
||||||
@@ -94,6 +122,17 @@ export class OfflineManager {
|
|||||||
// Deduplicate
|
// Deduplicate
|
||||||
const uniqueFiles = [...new Set(filesToFetch)];
|
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 = {
|
const progress: PreloadProgress = {
|
||||||
current: 0,
|
current: 0,
|
||||||
total: uniqueFiles.length,
|
total: uniqueFiles.length,
|
||||||
|
|||||||
+32
-1
@@ -292,7 +292,11 @@ export class SettingsView {
|
|||||||
<label class="flex items-center gap-1 cursor-pointer">
|
<label class="flex items-center gap-1 cursor-pointer">
|
||||||
<input type="checkbox" value="menaion" class="offline-type accent-gold"> <span class="text-sm">${t.settings.offlineMenaion}</span>
|
<input type="checkbox" value="menaion" class="offline-type accent-gold"> <span class="text-sm">${t.settings.offlineMenaion}</span>
|
||||||
</label>
|
</label>
|
||||||
|
<label class="flex items-center gap-1 cursor-pointer">
|
||||||
|
<input type="checkbox" value="bible" class="offline-type accent-gold"> <span class="text-sm">${t.settings.offlineBible}</span>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="bible-translations" class="hidden ml-4 mt-1 space-y-1 border-l border-gold/20 pl-3"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Storage info and controls -->
|
<!-- Storage info and controls -->
|
||||||
@@ -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) => `
|
||||||
|
<label class="flex items-center gap-1 cursor-pointer">
|
||||||
|
<input type="checkbox" value="${v.id}" class="bible-trans accent-gold">
|
||||||
|
<span class="text-xs text-navy">${v.name}</span>
|
||||||
|
<span class="text-[10px] text-navy-light">(${OfflineManager.getBibleSize(v.id) || '?'})</span>
|
||||||
|
</label>
|
||||||
|
`).join('');
|
||||||
|
} catch {}
|
||||||
|
});
|
||||||
|
|
||||||
// Offline content - auto-select ru+cu when ru is selected
|
// Offline content - auto-select ru+cu when ru is selected
|
||||||
document.querySelectorAll('.offline-lang').forEach(el => {
|
document.querySelectorAll('.offline-lang').forEach(el => {
|
||||||
el.addEventListener('change', () => {
|
el.addEventListener('change', () => {
|
||||||
@@ -438,8 +462,15 @@ export class SettingsView {
|
|||||||
const progressText = document.getElementById('offline-progress-text');
|
const progressText = document.getElementById('offline-progress-text');
|
||||||
if (progressDiv) progressDiv.classList.remove('hidden');
|
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
|
// 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 (progressBar) progressBar.style.width = '100%';
|
||||||
if (progressText) progressText.textContent = t.settings.offlineDone;
|
if (progressText) progressText.textContent = t.settings.offlineDone;
|
||||||
OfflineManager.getStats().then(stats => {
|
OfflineManager.getStats().then(stats => {
|
||||||
|
|||||||
Reference in new issue
Block a user