diff --git a/plugin-designs/bible-posters/bible-posters.vue b/plugin-designs/bible-posters/bible-posters.vue
index e43eeae89..1f29d2fae 100644
--- a/plugin-designs/bible-posters/bible-posters.vue
+++ b/plugin-designs/bible-posters/bible-posters.vue
@@ -43,7 +43,7 @@ const offeredBooks = computed(() => booksForTranslation(availableBookIds.value))
const plan = computed(() => (book.value ? planBiblePoster(size.value, book.value) : null))
const screen = computed(() => {
if (!book.value) return null
- return screenBiblePoster(book.value, size.value, { credit: translation.value.credit, dir: translation.value.dir })
+ return screenBiblePoster(book.value, size.value, { credit: translation.value.credit, dir: translation.value.dir, lang: translation.value.bcp47 })
})
// Which of the ladder's sizes fit this book, for the "which size" guidance.
@@ -89,7 +89,7 @@ async function loadBook() {
function print() {
if (!book.value) return
- const html = buildBiblePosterHtml(book.value, size.value, { credit: translation.value.credit, dir: translation.value.dir })
+ const html = buildBiblePosterHtml(book.value, size.value, { credit: translation.value.credit, dir: translation.value.dir, lang: translation.value.bcp47 })
printDocument(html, { labels: { print: t('biblePosters.print'), close: t('biblePosters.close') } })
}
@@ -160,7 +160,7 @@ onBeforeUnmount(() => { if (ro) ro.disconnect() })
diff --git a/plugin-designs/bible-posters/index.js b/plugin-designs/bible-posters/index.js
index 7d74642b4..757a92145 100644
--- a/plugin-designs/bible-posters/index.js
+++ b/plugin-designs/bible-posters/index.js
@@ -19,8 +19,9 @@ const profileModules = import.meta.glob('./*.vue')
const BiblePostersElement = defineMapElement({
profileModules,
configureApp: (app, { pinia, i18n }) => {
- // Install i18n AND hand the instance to ProfileLoader by reference; the element root cannot call
- // useI18n() itself, so this is how a host sets the plugin's interface language.
+ // defineMapElement already installs Pinia + i18n and provides the i18n instance to ProfileLoader
+ // (that is how a host sets the interface language). This hook is only for extra app-level provides;
+ // Bible Posters needs none.
},
})
diff --git a/plugin-designs/bible-posters/src/biblePoster.js b/plugin-designs/bible-posters/src/biblePoster.js
index 32afc7af0..774aa6f5a 100644
--- a/plugin-designs/bible-posters/src/biblePoster.js
+++ b/plugin-designs/bible-posters/src/biblePoster.js
@@ -132,13 +132,14 @@ function posterCss(plan, dir, unit) {
/** The print document: one page, the whole book. `texts` supplies the credit line and optional dir/lang. */
export function buildBiblePosterHtml(book, size, texts = {}) {
const dir = texts.dir || book.dir || 'ltr'
+ const lang = texts.lang || '' // a BCP-47 tag so hyphens:auto works and the page declares its language
const plan = planBiblePoster(size, book)
const css = '@page{size:' + size.widthIn + 'in ' + size.heightIn + 'in;margin:0}html,body{margin:0;padding:0}'
+ posterCss(plan, dir, 1)
const inner = '
' + esc(book.name) + '
'
+ '' + bodyHtml(book) + '
'
+ '' + esc(texts.credit || '') + '
'
- return ''
+ return ''
+ '' + esc(book.name) + ''
+ '' + inner + '
'
}
@@ -151,5 +152,5 @@ export function screenBiblePoster(book, size, texts = {}) {
const html = '' + esc(book.name) + '
'
+ '' + bodyHtml(book) + '
'
+ '' + esc(texts.credit || '') + '
'
- return { css, html, dir, plan, worldW: size.widthIn * PX_IN, worldH: size.heightIn * PX_IN }
+ return { css, html, dir, lang: texts.lang || '', plan, worldW: size.widthIn * PX_IN, worldH: size.heightIn * PX_IN }
}
diff --git a/plugin-designs/bible-posters/src/bibleSource.js b/plugin-designs/bible-posters/src/bibleSource.js
index dbfcd71de..62fd5560e 100644
--- a/plugin-designs/bible-posters/src/bibleSource.js
+++ b/plugin-designs/bible-posters/src/bibleSource.js
@@ -72,26 +72,35 @@ export async function fetchBook(translationId, bookId, { signal, onProgress, dir
const chapterBlocks = new Array(total) // keep chapter order regardless of fetch order
let done = 0
+ // One book is one atomic fetch: a chapter failure ABORTS the siblings (never a half book), while still
+ // honouring the caller's own signal. An internal controller chains to it so we can abort from inside.
+ const internal = new AbortController()
+ const onExt = () => internal.abort()
+ if (signal) { if (signal.aborted) internal.abort(); else signal.addEventListener('abort', onExt, { once: true }) }
+
async function one(ch) {
- const d = await getJson(`${API_BASE}/${translationId}/${bookId}/${ch}.json`, signal)
- const content = (d.chapter || d).content || []
- const blocks = []
- for (const item of content) {
- if (item.type === 'heading') { const t = itemText(item); if (t) blocks.push({ kind: 'heading', chapter: ch, text: t }) }
- else if (item.type === 'verse') { const t = itemText(item); if (t) blocks.push({ kind: 'verse', chapter: ch, number: item.number, text: t }) }
- }
- chapterBlocks[ch - 1] = blocks
- done += 1
- onProgress?.(done, total)
+ try {
+ const d = await getJson(`${API_BASE}/${translationId}/${bookId}/${ch}.json`, internal.signal)
+ const content = (d.chapter || d).content || []
+ const blocks = []
+ for (const item of content) {
+ if (item.type === 'heading') { const t = itemText(item); if (t) blocks.push({ kind: 'heading', chapter: ch, text: t }) }
+ else if (item.type === 'verse') { const t = itemText(item); if (t) blocks.push({ kind: 'verse', chapter: ch, number: item.number, text: t }) }
+ }
+ chapterBlocks[ch - 1] = blocks
+ done += 1
+ onProgress?.(done, total)
+ } catch (e) { internal.abort(); throw e } // stop the other workers on the first failure
}
// bounded-concurrency pool over chapters 1..total
const queue = []
for (let ch = 1; ch <= total; ch++) queue.push(ch)
const workers = new Array(Math.min(concurrency, total)).fill(0).map(async () => {
- while (queue.length) { const ch = queue.shift(); await one(ch) }
+ while (queue.length && !internal.signal.aborted) { const ch = queue.shift(); await one(ch) }
})
- await Promise.all(workers)
+ try { await Promise.all(workers) }
+ finally { if (signal) signal.removeEventListener('abort', onExt) }
const blocks = chapterBlocks.flat().filter(Boolean)
let verseCount = 0, charCount = 0
diff --git a/plugin-designs/bible-posters/src/translations.js b/plugin-designs/bible-posters/src/translations.js
index 8220b33e1..73024a880 100644
--- a/plugin-designs/bible-posters/src/translations.js
+++ b/plugin-designs/bible-posters/src/translations.js
@@ -34,29 +34,29 @@
/** v1 STRICT PUBLIC-DOMAIN seed — the packet's 6 + WEB. Every entry has all 66 books (OT+NT). */
export const TRANSLATIONS = [
- { id: 'eng_web', iso639_3: 'eng', language: 'English', endonym: 'English', name: 'World English Bible',
+ { id: 'eng_web', iso639_3: 'eng', bcp47: 'en', language: 'English', endonym: 'English', name: 'World English Bible',
dir: 'ltr', licence: 'public-domain',
- credit: 'World English Bible · public domain · ebible.org (via bible.helloao.org)',
+ credit: 'World English Bible · public domain · ebible.org · bible.helloao.org',
licenseUrl: 'https://ebible.org/Scriptures/details.php?id=eng-web' },
- { id: 'spa_r09', iso639_3: 'spa', language: 'Spanish', endonym: 'Español', name: 'Reina Valera 1909',
+ { id: 'spa_r09', iso639_3: 'spa', bcp47: 'es', language: 'Spanish', endonym: 'Español', name: 'Reina Valera 1909',
dir: 'ltr', licence: 'public-domain',
- credit: 'Reina Valera 1909 · dominio público · ebible.org (via bible.helloao.org)',
+ credit: 'Reina Valera 1909 · dominio público · ebible.org · bible.helloao.org',
licenseUrl: 'https://ebible.org/Scriptures/details.php?id=spaRV1909' },
- { id: 'fra_ost', iso639_3: 'fra', language: 'French', endonym: 'Français', name: 'French Ostervald Bible',
+ { id: 'fra_ost', iso639_3: 'fra', bcp47: 'fr', language: 'French', endonym: 'Français', name: 'French Ostervald Bible',
dir: 'ltr', licence: 'public-domain',
- credit: 'Bible Ostervald · domaine public · ebible.org (via bible.helloao.org)',
+ credit: 'Bible Ostervald · domaine public · ebible.org · bible.helloao.org',
licenseUrl: 'https://ebible.org/Scriptures/details.php?id=fra_fob' },
- { id: 'deu_l12', iso639_3: 'deu', language: 'German', endonym: 'Deutsch', name: 'German Luther Bible 1912',
+ { id: 'deu_l12', iso639_3: 'deu', bcp47: 'de', language: 'German', endonym: 'Deutsch', name: 'German Luther Bible 1912',
dir: 'ltr', licence: 'public-domain',
- credit: 'Lutherbibel 1912 · gemeinfrei · ebible.org (via bible.helloao.org)',
+ credit: 'Lutherbibel 1912 · gemeinfrei · ebible.org · bible.helloao.org',
licenseUrl: 'https://ebible.org/Scriptures/details.php?id=deu1912' },
- { id: 'rus_syn', iso639_3: 'rus', language: 'Russian', endonym: 'Русский', name: 'Russian Synodal Bible',
+ { id: 'rus_syn', iso639_3: 'rus', bcp47: 'ru', language: 'Russian', endonym: 'Русский', name: 'Russian Synodal Bible',
dir: 'ltr', licence: 'public-domain',
- credit: 'Синодальный перевод · общественное достояние · ebible.org (via bible.helloao.org)',
+ credit: 'Синодальный перевод · общественное достояние · ebible.org · bible.helloao.org',
licenseUrl: 'https://ebible.org/Scriptures/details.php?id=russyn' },
- { id: 'arb_vdv', iso639_3: 'arb', language: 'Arabic', endonym: 'العربية', name: 'Arabic Van Dyck Bible',
+ { id: 'arb_vdv', iso639_3: 'arb', bcp47: 'ar', language: 'Arabic', endonym: 'العربية', name: 'Arabic Van Dyck Bible',
dir: 'rtl', licence: 'public-domain',
- credit: 'فان دايك · ملك عام · ebible.org (via bible.helloao.org)',
+ credit: 'فان دايك · ملك عام · ebible.org · bible.helloao.org',
licenseUrl: 'https://ebible.org/Scriptures/details.php?id=arb-vd' },
]